From af2a932a0229ba5c801824ad03a463b182895dca Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Thu, 17 Jul 2025 18:31:53 +1200
Subject: [PATCH 001/146] Display removable health vials project
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index 8801263..cb0f833 100644
--- a/README.md
+++ b/README.md
@@ -74,4 +74,5 @@ You can also join us on our
+
From 5404e8ee32079f5d35c8452d81a1ebbb28171f8a Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Thu, 24 Jul 2025 19:32:34 +1200
Subject: [PATCH 002/146] Remove unused variable
---
scripts/vscripts/alyxlib/panorama/core.lua | 1 -
1 file changed, 1 deletion(-)
diff --git a/scripts/vscripts/alyxlib/panorama/core.lua b/scripts/vscripts/alyxlib/panorama/core.lua
index 86eb7d7..0aaec2b 100644
--- a/scripts/vscripts/alyxlib/panorama/core.lua
+++ b/scripts/vscripts/alyxlib/panorama/core.lua
@@ -55,7 +55,6 @@ function Panorama:Send(panelEntity, ...)
local dataString = id .. "|"
local data = {...}
- local i = 1
local flattenedData = {}
From ab1841c3cc95b7b906240d15f8194d2841c73456 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Thu, 24 Jul 2025 19:32:52 +1200
Subject: [PATCH 003/146] Fix label using incorrect parent
---
panorama/scripts/custom_game/alyxlib_debug_menu.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/panorama/scripts/custom_game/alyxlib_debug_menu.js b/panorama/scripts/custom_game/alyxlib_debug_menu.js
index bce0f99..ecd4d96 100644
--- a/panorama/scripts/custom_game/alyxlib_debug_menu.js
+++ b/panorama/scripts/custom_game/alyxlib_debug_menu.js
@@ -782,7 +782,7 @@ class SubMenuLabel
*/
AddToPanel(panel)
{
- this.panel = CreatePanel("Label", this.content, this.id, "custom_label");
+ this.panel = CreatePanel("Label", panel, this.id, "custom_label");
this.panel.text = this.text;
}
From 2001359ac17e58e16f0f66e9fa739259f90fe10c Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Tue, 2 Sep 2025 16:00:59 +1200
Subject: [PATCH 004/146] Handle nil pattern
---
scripts/vscripts/alyxlib/debug/common.lua | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/scripts/vscripts/alyxlib/debug/common.lua b/scripts/vscripts/alyxlib/debug/common.lua
index 0962164..241d667 100644
--- a/scripts/vscripts/alyxlib/debug/common.lua
+++ b/scripts/vscripts/alyxlib/debug/common.lua
@@ -22,9 +22,13 @@ Debug.version = "v2.2.0"
---
---@param pattern string # The search pattern to look for.
---@param exact boolean? # If true the pattern must match exactly, otherwise wildcards will be used.
----@return EntityHandle
+---@return EntityHandle? # The found entity, or nil if not found.
function Debug.FindEntityByPattern(pattern, exact)
+ if pattern == nil then
+ return nil
+ end
+
if Debug.IsEntityHandleString(pattern) then
return Debug.FindEntityByHandleString(pattern)
end
From 6032b120aaa9956248a9dd8b5c3d7d1ec35c100f Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Tue, 2 Sep 2025 16:01:58 +1200
Subject: [PATCH 005/146] Handle nil entity handle string
---
scripts/vscripts/alyxlib/debug/common.lua | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/scripts/vscripts/alyxlib/debug/common.lua b/scripts/vscripts/alyxlib/debug/common.lua
index 241d667..59e502e 100644
--- a/scripts/vscripts/alyxlib/debug/common.lua
+++ b/scripts/vscripts/alyxlib/debug/common.lua
@@ -900,8 +900,9 @@ end
---Gets whether the string is in the format of an entity handle.
---
---@param handleString string # The handle string
----@return string # The hash part or nil if not an entity handle
+---@return string? # The hash part or nil if not an entity handle
function Debug.IsEntityHandleString(handleString)
+ if handleString == nil then return nil end
local mtc = handleString:match("^(table:?%s*)")
if mtc then
handleString = handleString:sub(#mtc+1)
From 001c0032c5a9217b52c18e8ad2e31317d48a2b45 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Tue, 2 Sep 2025 16:10:01 +1200
Subject: [PATCH 006/146] Add new alyxlib.player.data
This aims to provide more granular control over how and when weapon
switching is updated, specifically for addons that manipulate player
inventory
---
scripts/vscripts/alyxlib/player/core.lua | 18 +-
scripts/vscripts/alyxlib/player/data.lua | 195 +++++++++++++++++++++
scripts/vscripts/alyxlib/player/events.lua | 84 ++-------
3 files changed, 222 insertions(+), 75 deletions(-)
create mode 100644 scripts/vscripts/alyxlib/player/data.lua
diff --git a/scripts/vscripts/alyxlib/player/core.lua b/scripts/vscripts/alyxlib/player/core.lua
index 5739697..8fc2b38 100644
--- a/scripts/vscripts/alyxlib/player/core.lua
+++ b/scripts/vscripts/alyxlib/player/core.lua
@@ -143,6 +143,9 @@ CBasePlayer.Items = {
---The hlvr_multitool
---@type EntityHandle
multitool = nil,
+ ---The current hlvr_weapon_generic_pistol equipped
+ ---@type EntityHandle
+ generic_pistol = nil,
---List of generic pistols the player has
---@type EntityHandle[]
genericpistols = {},
@@ -609,15 +612,20 @@ end
---@return EntityHandle|nil
function CBasePlayer:GetWeapon()
if self.CurrentlyEquipped == PLAYER_WEAPON_ENERGYGUN then
- return Entities:FindByClassnameNearest("hlvr_weapon_energygun", self.PrimaryHand:GetPalmPosition(), 128)--[[@as EntityHandle]]
+ return self.Items.weapons.energygun
+ or Entities:FindByClassnameNearest("hlvr_weapon_energygun", self.PrimaryHand:GetPalmPosition(), 128)--[[@as EntityHandle]]
elseif self.CurrentlyEquipped == PLAYER_WEAPON_RAPIDFIRE then
- return Entities:FindByClassnameNearest("hlvr_weapon_rapidfire", self.PrimaryHand:GetPalmPosition(), 128)--[[@as EntityHandle]]
+ return self.Items.weapons.rapidfire
+ or Entities:FindByClassnameNearest("hlvr_weapon_rapidfire", self.PrimaryHand:GetPalmPosition(), 128)--[[@as EntityHandle]]
elseif self.CurrentlyEquipped == PLAYER_WEAPON_SHOTGUN then
- return Entities:FindByClassnameNearest("hlvr_weapon_shotgun", self.PrimaryHand:GetPalmPosition(), 128)--[[@as EntityHandle]]
+ return self.Items.weapons.shotgun
+ or Entities:FindByClassnameNearest("hlvr_weapon_shotgun", self.PrimaryHand:GetPalmPosition(), 128)--[[@as EntityHandle]]
elseif self.CurrentlyEquipped == PLAYER_WEAPON_GENERIC_PISTOL then
- return Entities:FindByClassnameNearest("hlvr_weapon_generic_pistol", self.PrimaryHand:GetPalmPosition(), 128)--[[@as EntityHandle]]
+ return self.Items.weapons.generic_pistol
+ or Entities:FindByClassnameNearest("hlvr_weapon_generic_pistol", self.PrimaryHand:GetPalmPosition(), 128)--[[@as EntityHandle]]
elseif self.CurrentlyEquipped == PLAYER_WEAPON_MULTITOOL then
- return Entities:FindByClassnameNearest("hlvr_multitool", self.PrimaryHand:GetPalmPosition(), 128)--[[@as EntityHandle]]
+ return self.Items.weapons.multitool
+ or Entities:FindByClassnameNearest("hlvr_multitool", self.PrimaryHand:GetPalmPosition(), 128)--[[@as EntityHandle]]
else
return nil
end
diff --git a/scripts/vscripts/alyxlib/player/data.lua b/scripts/vscripts/alyxlib/player/data.lua
new file mode 100644
index 0000000..689da23
--- /dev/null
+++ b/scripts/vscripts/alyxlib/player/data.lua
@@ -0,0 +1,195 @@
+--[[
+ v1.0.0
+ https://github.com/FrostSource/alyxlib
+
+ Advanced player-specific data management and logic handling.
+
+ This module is designed as a library component and is not
+ automatically loaded into the global scope.
+
+ Usage:
+ local PlayerData = require("alyxlib.player.data")
+]]
+
+local version = "v1.0.0"
+
+---
+---Forces all persistent player data to save.
+---
+---[CBasePlayer.Items](lua://CBasePlayer.Items)\
+---[CBasePlayer.CurrentlyEquipped](lua://CBasePlayer.CurrentlyEquipped)\
+---[CBasePlayer.LeftHand.WristItem](lua://CPropVRHand.WristItem)\
+---[CBasePlayer.RightHand.WristItem](lua://CPropVRHand.WristItem)
+---
+local function savePlayerData()
+ Storage.SaveTable(Player, "PlayerItems", Player.Items)
+
+ -- Weapons aren't re-equipped on load so we need to save this
+ Storage.SaveString(Player, "PlayerCurrentlyEquipped", Player.CurrentlyEquipped)
+
+ if Player and Player.LeftHand then
+ Storage.SaveEntity(Player, "LeftWristItem", Player.LeftHand.WristItem)
+ end
+ if Player and Player.RightHand then
+ Storage.SaveEntity(Player, "RightWristItem", Player.RightHand.WristItem)
+ end
+end
+
+---
+---Loads all persistent player data.
+---
+local function loadPlayerData()
+ Player.Items = Storage.LoadTable(Player, "PlayerItems", Player.Items)
+
+ Player.CurrentlyEquipped = Storage.LoadString(Player, "PlayerCurrentlyEquipped", Player.CurrentlyEquipped)
+
+ if Player and Player.LeftHand then
+ Player.LeftHand.WristItem = Storage.LoadEntity(Player, "LeftWristItem", Player.LeftHand.WristItem)
+ end
+ if Player and Player.RightHand then
+ Player.RightHand.WristItem = Storage.LoadEntity(Player, "RightWristItem", Player.RightHand.WristItem)
+ end
+end
+
+local previouslyEquipped = nil
+local currentlyEquipped = nil
+local previousHandle = nil
+local currentHandle = nil
+
+local syncPaused = false
+
+---
+---Sync the equipped weapon state of the player with the given weapon classname.
+---
+---If no classname is given, the weapon classname will be determined from the player's criteria.
+---
+---@param classname? string # The classname of the weapon to sync.
+---@return EntityHandle? weaponHandle # The entity handle of the weapon that was equipped.
+local function SyncEquippedWeaponState(classname, handle)
+ if syncPaused then return end
+
+ if classname == nil then
+ local criteria = Player:GetCriteria()
+ classname = criteria.primaryhand_active_attachment
+ end
+
+ -- print("Swapping from", Player.CurrentlyEquipped, "to", classname)
+
+ -- Cache current state so it can be restored later
+ -- print("Saving equipped", Player.PreviouslyEquipped, Player.CurrentlyEquipped)
+ previouslyEquipped = Player.PreviouslyEquipped
+ currentlyEquipped = Player.CurrentlyEquipped
+ previousHandle = currentHandle
+
+ Player.PreviouslyEquipped = Player.CurrentlyEquipped
+
+ ---@type EntityHandle
+ local weaponHandle = nil
+
+ if classname == "hand_use_controller" then
+ Player.CurrentlyEquipped = PLAYER_WEAPON_HAND
+ weaponHandle = Player.PrimaryHand
+ currentHandle = nil
+ else
+ weaponHandle = handle or Entities:FindBestMatching("", classname, Player.PrimaryHand:GetPalmPosition(), 64)
+ -- if weaponHandle then
+ -- print("Setting new weapon for", classname, weaponHandle)
+ -- Debug.Sphere(weaponHandle:GetOrigin(), 2, 10)
+ -- end
+
+ if not IsValidEntity(weaponHandle) then
+ warn("Could not find weapon entity for equipped weapon: " .. classname)
+ end
+
+ currentHandle = weaponHandle
+
+ if classname == "hlvr_weapon_energygun" then
+ Player.CurrentlyEquipped = PLAYER_WEAPON_ENERGYGUN
+ if not IsValidEntity(Player.Items.weapons.energygun) then
+ Player.Items.weapons.energygun = weaponHandle
+ else
+ weaponHandle = Player.Items.weapons.energygun
+ end
+ elseif classname == "hlvr_weapon_rapidfire" then
+ Player.CurrentlyEquipped = PLAYER_WEAPON_RAPIDFIRE
+ if not IsValidEntity(Player.Items.weapons.rapidfire) then
+ Player.Items.weapons.rapidfire = weaponHandle
+ else
+ weaponHandle = Player.Items.weapons.rapidfire
+ end
+ elseif classname == "hlvr_weapon_shotgun" then
+ Player.CurrentlyEquipped = PLAYER_WEAPON_SHOTGUN
+ if not IsValidEntity(Player.Items.weapons.shotgun) then
+ Player.Items.weapons.shotgun = weaponHandle
+ else
+ weaponHandle = Player.Items.weapons.shotgun
+ end
+ elseif classname == "hlvr_multitool" then
+ Player.CurrentlyEquipped = PLAYER_WEAPON_MULTITOOL
+ if not IsValidEntity(Player.Items.weapons.multitool) then
+ Player.Items.weapons.multitool = weaponHandle
+ else
+ weaponHandle = Player.Items.weapons.multitool
+ end
+ elseif classname == "hlvr_weapon_generic_pistol" then
+ Player.CurrentlyEquipped = PLAYER_WEAPON_GENERIC_PISTOL
+ Player.Items.weapons.generic_pistol = weaponHandle
+
+ -- Add this custom pistol if this is the first time encountering it
+ if not vlua.find(Player.Items.weapons.genericpistols, weaponHandle) then
+ table.insert(Player.Items.weapons.genericpistols, weaponHandle)
+ end
+ end
+ end
+
+ Player:UpdateWeaponsExistence()
+
+ -- This event can fire before the player has a hand
+ if Player.PrimaryHand then
+ Player.PrimaryHand.ItemHeld = weaponHandle
+ end
+
+ savePlayerData()
+
+ return weaponHandle
+end
+
+---
+---Restore the previously equipped weapon state.
+---
+---@param fireEvent? boolean # Forces the `weapon_switch` event to be fired when restoring.
+local function RestorePreviouslyEquippedWeaponState(fireEvent)
+ Player.CurrentlyEquipped = previouslyEquipped
+ if fireEvent then
+ ---@type GameEventWeaponSwitch
+ FireGameEvent("weapon_switch", {
+ item = currentlyEquipped,
+ })
+ else
+ SyncEquippedWeaponState(currentlyEquipped, previousHandle)
+ end
+end
+
+---
+---Pause weapon state synching. This will prevent the `weapon_switch` player event from firing.
+---
+local function PauseWeaponStateSync()
+ syncPaused = true
+end
+
+---
+---Resume weapon state synching. This will allow the `weapon_switch` player event to fire.
+---
+local function ResumeWeaponStateSync()
+ syncPaused = false
+end
+
+return {
+ version = version,
+ SyncEquippedWeaponState = SyncEquippedWeaponState,
+ RestorePreviouslyEquippedWeaponState = RestorePreviouslyEquippedWeaponState,
+ PauseWeaponStateSync = PauseWeaponStateSync,
+ ResumeWeaponStateSync = ResumeWeaponStateSync,
+ SavePlayerData = savePlayerData,
+ LoadPlayerData = loadPlayerData
+}
\ No newline at end of file
diff --git a/scripts/vscripts/alyxlib/player/events.lua b/scripts/vscripts/alyxlib/player/events.lua
index 16fee2b..1f4fc6e 100644
--- a/scripts/vscripts/alyxlib/player/events.lua
+++ b/scripts/vscripts/alyxlib/player/events.lua
@@ -7,6 +7,8 @@
local version = "v2.2.0"
+local playerData = require "alyxlib.player.data"
+
---@class __PlayerRegisteredEventData
---@field callback function
---@field context any
@@ -102,35 +104,7 @@ local player_weapon_to_ammotype =
[PLAYER_WEAPON_GENERIC_PISTOL] = "AlyxGun",
}
----
----Save the Player.Items table
----
-local function savePlayerData()
- Storage.SaveTable(Player, "PlayerItems", Player.Items)
- -- Weapons aren't re-equipped on load so we need to save this
- Storage.SaveString(Player, "PlayerCurrentlyEquipped", Player.CurrentlyEquipped)
-
- if Player and Player.LeftHand then
- Storage.SaveEntity(Player, "LeftWristItem", Player.LeftHand.WristItem)
- end
- if Player and Player.RightHand then
- Storage.SaveEntity(Player, "RightWristItem", Player.RightHand.WristItem)
- end
-end
-
-local function loadPlayerData()
- Player.Items = Storage.LoadTable(Player, "PlayerItems", Player.Items)
-
- Player.CurrentlyEquipped = Storage.LoadString(Player, "PlayerCurrentlyEquipped", Player.CurrentlyEquipped)
-
- if Player and Player.LeftHand then
- Player.LeftHand.WristItem = Storage.LoadEntity(Player, "LeftWristItem", Player.LeftHand.WristItem)
- end
- if Player and Player.RightHand then
- Player.RightHand.WristItem = Storage.LoadEntity(Player, "RightWristItem", Player.RightHand.WristItem)
- end
-end
---Callback logic for every player event.
---@param eventName string # Name of the event, data.game_event_name
@@ -158,7 +132,7 @@ end
local function listenEventPlayerActivate(data)
playerActivated = true
Player = GetListenServerHost()
- loadPlayerData()
+ playerData.LoadPlayerData()
local previous_map = Storage.LoadString(Player, "PlayerPreviousMap", "")
Storage.SaveString(Player, "PlayerPreviousMap", GetMapName())
@@ -472,7 +446,7 @@ local function listenEventPlayerDropAmmoInBackpack(data)
else
warn("Couldn't figure out ammo for "..tostring(ammotype))
end
- savePlayerData()
+ playerData.SavePlayerData()
-- Registered callback
local newdata = vlua.clone(data)--[[@as PlayerEventPlayerDropAmmoInBackpack]]
@@ -521,7 +495,7 @@ local function listenEventPlayerRetrievedBackpackClip(data)
Player.Items.ammo.shotgun = Player.Items.ammo.shotgun - ammo_amount
end
- savePlayerData()
+ playerData.SavePlayerData()
-- Registered callback
newdata.ammo_amount = ammo_amount
@@ -535,7 +509,7 @@ local function listenEventPlayerRetrievedBackpackClip(data)
-- Registered callback
if do_callback then
- savePlayerData()
+ playerData.SavePlayerData()
newdata.ammo_amount = ammo_amount
eventCallback(data.game_event_name, newdata)
end
@@ -573,7 +547,7 @@ local function listenEventPlayerStoredItemInItemholder(data)
-- elseif data.item == "item_healthvial" then
-- Player.Items.healthpen = Player.Items.healthpen + 1
-- end
- savePlayerData()
+ playerData.SavePlayerData()
-- Registered callback
local newdata = vlua.clone(data)--[[@as PlayerEventPlayerStoredItemInItemholder]]
@@ -620,7 +594,7 @@ local function listenEventPlayerRemovedItemFromItemholder(data)
-- elseif data.item == "item_healthvial" then
-- Player.Items.healthpen = Player.Items.healthpen - 1
-- end
- savePlayerData()
+ playerData.SavePlayerData()
-- Registered callback
local newdata = vlua.clone(data)--[[@as PlayerEventPlayerRemovedItemFromItemholder]]
@@ -673,46 +647,16 @@ local function listenEventWeaponSwitch(data)
-- Debug.PrintTable(data)
-- print("\n")
- Player.PreviouslyEquipped = Player.CurrentlyEquipped
-
- ---@type EntityHandle
- local weaponHandle = nil
-
- if data.item == "hand_use_controller" then
- Player.CurrentlyEquipped = PLAYER_WEAPON_HAND
- else
- weaponHandle = Entities:FindBestMatching("", data.item, Player.PrimaryHand:GetPalmPosition(), 64)
- if data.item == "hlvr_weapon_energygun" then
- Player.CurrentlyEquipped = PLAYER_WEAPON_ENERGYGUN
- Player.Items.weapons.energygun = weaponHandle
- elseif data.item == "hlvr_weapon_rapidfire" then
- Player.CurrentlyEquipped = PLAYER_WEAPON_RAPIDFIRE
- Player.Items.weapons.rapidfire = weaponHandle
- elseif data.item == "hlvr_weapon_shotgun" then
- Player.CurrentlyEquipped = PLAYER_WEAPON_SHOTGUN
- Player.Items.weapons.shotgun = weaponHandle
- elseif data.item == "hlvr_multitool" then
- Player.CurrentlyEquipped = PLAYER_WEAPON_MULTITOOL
- Player.Items.weapons.multitool = weaponHandle
- elseif data.item == "hlvr_weapon_generic_pistol" then
- Player.CurrentlyEquipped = PLAYER_WEAPON_GENERIC_PISTOL
-
- -- Add this custom pistol if this is the first time encountering it
- if not vlua.find(Player.Items.weapons.genericpistols, weaponHandle) then
- table.insert(Player.Items.weapons.genericpistols, weaponHandle)
- end
- end
- end
+ local weaponHandle = playerData.SyncEquippedWeaponState(data.item)
- Player:UpdateWeaponsExistence()
+ -- Nil return means sync in paused
+ if not weaponHandle then return end
- -- This event can fire before the player has a hand
- if Player.PrimaryHand then
- Player.PrimaryHand.ItemHeld = weaponHandle
+ -- Hand entity isn't returned, so nil it
+ if weaponHandle == Player.LeftHand or weaponHandle == Player.RightHand then
+ weaponHandle = nil
end
- savePlayerData()
-
-- Registered callback
local newdata = vlua.clone(data)--[[@as PlayerEventWeaponSwitch]]
newdata.item = weaponHandle
From 6aece7bfe689a3410865616a2960ce6e91224214 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Tue, 2 Sep 2025 16:10:52 +1200
Subject: [PATCH 007/146] Add command ent_print_children
---
scripts/vscripts/alyxlib/debug/commands.lua | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/scripts/vscripts/alyxlib/debug/commands.lua b/scripts/vscripts/alyxlib/debug/commands.lua
index 8adad09..6b2b6c2 100644
--- a/scripts/vscripts/alyxlib/debug/commands.lua
+++ b/scripts/vscripts/alyxlib/debug/commands.lua
@@ -484,6 +484,21 @@ RegisterAlyxLibCommand("ent_rename", function(_, pattern, newName)
end
end, "Renames the first entity found using a pattern to a new name", 0)
+---
+---Prints the children of an entity found using `pattern`.
+---
+RegisterAlyxLibCommand("ent_print_children", function(_, pattern)
+ local ent = Debug.FindEntityByPattern(pattern)
+ if not ent then
+ warn("Could not find entity with pattern '"..pattern.."'")
+ return
+ end
+
+ print(entstr(ent))
+
+ Debug.PrintEntityList(ent:GetChildrenMemSafe())
+end)
+
local symbols = {"and","break","do","else","elseif","end","false","for","function","if","in","local","nil","not","or","repeat","return","then","true","until","while"}
-- if IsInToolsMode() then
From 98ba3e55459593f88dacb5b836de8bee9725888f Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Tue, 2 Sep 2025 16:11:23 +1200
Subject: [PATCH 008/146] Fix deprecated items being referenced
---
scripts/vscripts/alyxlib/player/core.lua | 11 -----------
1 file changed, 11 deletions(-)
diff --git a/scripts/vscripts/alyxlib/player/core.lua b/scripts/vscripts/alyxlib/player/core.lua
index 8fc2b38..02a018a 100644
--- a/scripts/vscripts/alyxlib/player/core.lua
+++ b/scripts/vscripts/alyxlib/player/core.lua
@@ -356,7 +356,6 @@ function CBasePlayer:AddResources(pistol_ammo, rapidfire_ammo, shotgun_ammo, res
nil,
self.Items.ammo.rapidfire + rapidfire_ammo,
self.Items.ammo.shotgun + shotgun_ammo,
- nil,nil,nil,
self.Items.resin + resin
)
end
@@ -379,7 +378,6 @@ function CBasePlayer:SetResources(pistol_ammo, rapidfire_ammo, shotgun_ammo, res
nil,
rapidfire_ammo or self.Items.ammo.rapidfire,
shotgun_ammo or self.Items.ammo.shotgun,
- nil,nil,nil,
resin or self.Items.resin
)
end
@@ -391,27 +389,18 @@ end
---@param generic_pistol_ammo? integer
---@param rapidfire_ammo? integer
---@param shotgun_ammo? integer
----@param frag_grenades? integer
----@param xen_grenades? integer
----@param healthpens? integer
---@param resin? integer
function CBasePlayer:SetItems(
energygun_ammo,
generic_pistol_ammo,
rapidfire_ammo,
shotgun_ammo,
- frag_grenades,
- xen_grenades,
- healthpens,
resin
)
self.Items.ammo.energygun = energygun_ammo or self.Items.ammo.energygun
self.Items.ammo.generic_pistol = generic_pistol_ammo or self.Items.ammo.generic_pistol
self.Items.ammo.rapidfire = rapidfire_ammo or self.Items.ammo.rapidfire
self.Items.ammo.shotgun = shotgun_ammo or self.Items.ammo.shotgun
- self.Items.grenades.frag = frag_grenades or self.Items.grenades.frag
- self.Items.grenades.xen = xen_grenades or self.Items.grenades.xen
- self.Items.healthpen = healthpens or self.Items.healthpen
self.Items.resin = resin or self.Items.resin
---@TODO: Consider moving save function above this
From f0f303e5e88b7d852c9b8de929bcb933022ccfe8 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Tue, 2 Sep 2025 16:13:27 +1200
Subject: [PATCH 009/146] Add debug menu cycler numbers to show the number of
hidden categories
---
.../layout/custom_game/alyxlib_debug_menu.xml | 2 ++
.../scripts/custom_game/alyxlib_debug_menu.js | 8 ++++++
.../styles/custom_game/alyxlib_debug_menu.css | 25 +++++++++++--------
3 files changed, 25 insertions(+), 10 deletions(-)
diff --git a/panorama/layout/custom_game/alyxlib_debug_menu.xml b/panorama/layout/custom_game/alyxlib_debug_menu.xml
index 3551557..fcebabb 100644
--- a/panorama/layout/custom_game/alyxlib_debug_menu.xml
+++ b/panorama/layout/custom_game/alyxlib_debug_menu.xml
@@ -38,6 +38,7 @@
@@ -46,6 +47,7 @@
diff --git a/panorama/scripts/custom_game/alyxlib_debug_menu.js b/panorama/scripts/custom_game/alyxlib_debug_menu.js
index ecd4d96..3ffdf5f 100644
--- a/panorama/scripts/custom_game/alyxlib_debug_menu.js
+++ b/panorama/scripts/custom_game/alyxlib_debug_menu.js
@@ -850,6 +850,12 @@ function UpdateCategoryBarVisibility()
categoryBarCycleIndex = selectedIndex - numberOfVisibleCategories + 1;
}
+ const leftCategoryCount = categoryBarCycleIndex;
+ $("#CycleCategoryLeftButtonLabel").text = leftCategoryCount > 0 ? leftCategoryCount.toString() : "";
+
+ const rightCategoryCount = Math.max(0, categories.length - categoryBarCycleIndex - numberOfVisibleCategories);
+ $("#CycleCategoryRightButtonLabel").text = rightCategoryCount > 0 ? rightCategoryCount.toString() : "";
+
categories.forEach((category, index) => {
const isVisible = index >= categoryBarCycleIndex && index < categoryBarCycleIndex + numberOfVisibleCategories;
category.SetBarButtonVisible(isVisible);
@@ -872,6 +878,8 @@ function CreateCategory(id, name)
if (currentlySelectedCategory == null)
SetCategoryVisible(category.id);
+
+ UpdateCategoryBarVisibility();
return category;
}
diff --git a/panorama/styles/custom_game/alyxlib_debug_menu.css b/panorama/styles/custom_game/alyxlib_debug_menu.css
index 57cf28d..bcef003 100644
--- a/panorama/styles/custom_game/alyxlib_debug_menu.css
+++ b/panorama/styles/custom_game/alyxlib_debug_menu.css
@@ -114,25 +114,30 @@ Button.custom_button_with_subtitle.error_msg:not(.disabled) Label.button_label.s
}
.CycleCategoryLeft
{
- flow-children: left;
- horizontal-align: left;
+ flow-children: none;
overflow: clip clip;
- /* background-color: red; */
width: 10%;
}
+.CycleCategoryLeft Label
+{
+ font-size: 22px;
+ text-align: left;
+ transform: translateX(5px);
+ width: 50%;
+}
.CycleCategoryRight
{
- flow-children: left;
- horizontal-align: right;
+ flow-children: none;
overflow: clip clip;
- /* background-color: blue; */
width: 10%;
}
-/* .cycle_image
+.CycleCategoryRight Label
{
- horizontal-align: left;
- margin: 0px 40px 10px 10px;
-} */
+ font-size: 22px;
+ text-align: right;
+ transform: translateX(-58px);
+ width: 50%;
+}
.CategoryBar
{
From 900d2b36ef0ec4a7261be3b71ea703f598cb4f77 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Tue, 2 Sep 2025 16:19:26 +1200
Subject: [PATCH 010/146] Hide noclip_vr button outside vr
---
scripts/vscripts/alyxlib/debug/debug_menu.lua | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index 46cd72e..967a4db 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -780,12 +780,14 @@ DebugMenu:AddCategory(categoryId, "AlyxLib")
DebugMenu:AddSeparator(categoryId, nil, "Basic")
-DebugMenu:AddToggle(categoryId, "noclip_vr", "NoClip VR", "noclip_vr", function ()
- return Convars:GetBool("noclip_vr_enabled")
-end)
-DebugMenu:AddLabel(categoryId, "noclip_vr_label", "Hold movement trigger to boost")
-DebugMenu:AddSlider(categoryId, "noclip_vr_speed", "NoClip VR Speed", 0.5, 10, false, "noclip_vr_speed", 2)
-DebugMenu:AddSlider(categoryId, "noclip_vr_boost_speed", "NoClip VR Boost Speed", 0.5, 10, false, "noclip_vr_boost_speed", 2)
+if IsVREnabled() then
+ DebugMenu:AddToggle(categoryId, "noclip_vr", "NoClip VR", "noclip_vr", function ()
+ return Convars:GetBool("noclip_vr_enabled")
+ end)
+ DebugMenu:AddLabel(categoryId, "noclip_vr_label", "Hold movement trigger to boost")
+ DebugMenu:AddSlider(categoryId, "noclip_vr_speed", "NoClip VR Speed", 0.5, 10, false, "noclip_vr_speed", 2)
+ DebugMenu:AddSlider(categoryId, "noclip_vr_boost_speed", "NoClip VR Boost Speed", 0.5, 10, false, "noclip_vr_boost_speed", 2)
+end
DebugMenu:AddToggle(categoryId, "buddha", "Buddha Mode", "buddha")
From e4ebc0d92ab0996821d7f427a658e49879d43659 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Tue, 2 Sep 2025 16:23:46 +1200
Subject: [PATCH 011/146] Handle dead class entities
---
scripts/vscripts/alyxlib/class.lua | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/scripts/vscripts/alyxlib/class.lua b/scripts/vscripts/alyxlib/class.lua
index 76bc6e6..c07cbbc 100644
--- a/scripts/vscripts/alyxlib/class.lua
+++ b/scripts/vscripts/alyxlib/class.lua
@@ -457,6 +457,8 @@ end
---@param value? any # Value to save. If not provided the value will be retrieved from the field with the same `name`.
---@luadoc-ignore
function EntityClass:Save(name, value)
+ if not IsValidEntity(self) then return end
+
if name then
Storage.Save(self, name, value~=nil and value or self[name])
end
@@ -477,7 +479,12 @@ end
---@luadoc-ignore
function EntityClass:ResumeThink()
if not self:IsNull() then
- self:SetContextThink("__EntityThink", function() return self:Think() end, 0)
+ self:SetContextThink("__EntityThink", function()
+ -- Handle dead entity and user PauseThink used without returning nil
+ if not IsValidEntity(self) or not self.IsThinking then return nil end
+
+ return self:Think()
+ end, 0)
self.IsThinking = true
end
end
From 547f68ff5e2f4fff5950ff1b3d00029ff7f07e96 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Tue, 2 Sep 2025 16:54:20 +1200
Subject: [PATCH 012/146] Add command hlvr_give_grabbity_gloves
---
scripts/vscripts/alyxlib/debug/debug_menu.lua | 4 ++++
scripts/vscripts/alyxlib/debug/vr.lua | 12 ++++++++++++
2 files changed, 16 insertions(+)
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index 967a4db..95d0cb0 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -804,6 +804,10 @@ end)
DebugMenu:AddSeparator(categoryId, nil, "Equipment")
+if IsVREnabled() or IsFakeVREnabled() then
+ DebugMenu:AddButton(categoryId, "givegrabbity", "Give Grabbity Gloves", "hlvr_give_grabbity_gloves")
+end
+
DebugMenu:AddButton(categoryId, "giveammo", "Give 999 Ammo", function()
SendToConsole("hlvr_setresources 999 999 999 " .. Player:GetResin())
end)
diff --git a/scripts/vscripts/alyxlib/debug/vr.lua b/scripts/vscripts/alyxlib/debug/vr.lua
index 9a5be9d..3942939 100644
--- a/scripts/vscripts/alyxlib/debug/vr.lua
+++ b/scripts/vscripts/alyxlib/debug/vr.lua
@@ -102,6 +102,18 @@ Convars:RegisterCommand("add_hand_attachment", function (_, classname, handName)
hand:AddHandAttachment(ent)
end, "", 0)
+RegisterAlyxLibCommand("hlvr_give_grabbity_gloves", function (_)
+ if not Player:HasGrabbityGloves() then
+ local equip = SpawnEntityFromTableSynchronous("info_hlvr_equip_player", {
+ grabbitygloves = "1",
+ itemholder = Player:HasItemHolder() and "1" or "0",
+ backpack_enabled = Player:GetBackpack() ~= nil and "1" or "0", -- this is not an accurate check
+ })
+ equip:EntFire("EquipNow")
+ equip:EntFire("Kill", nil, 0.1)
+ end
+end, "Gives the player grabbity gloves", FCVAR_NONE)
+
---Hidden convar state that can be retrieved by other scripts
RegisterAlyxLibConvar("noclip_vr_enabled", "0", "True if noclip_vr is enabled (readonly)", FCVAR_HIDDEN)
From 052f6fc18f96d3f79d3cb643b48ebd7095b20a18 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Tue, 2 Sep 2025 16:54:41 +1200
Subject: [PATCH 013/146] Load alyxlib.debug.vr in fake vr
---
scripts/vscripts/alyxlib/init.lua | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/scripts/vscripts/alyxlib/init.lua b/scripts/vscripts/alyxlib/init.lua
index 4b5439b..9b032fb 100644
--- a/scripts/vscripts/alyxlib/init.lua
+++ b/scripts/vscripts/alyxlib/init.lua
@@ -88,7 +88,7 @@ alyxlib_require "alyxlib.panorama.core"
alyxlib_require "alyxlib.debug.common"
alyxlib_require "alyxlib.debug.commands"
alyxlib_require "alyxlib.debug.controller"
-if IsVREnabled() then
+if IsVREnabled() or IsFakeVREnabled() then
alyxlib_require "alyxlib.debug.vr"
else
alyxlib_require "alyxlib.debug.novr"
From 90c6d9adfe33e4e7d5e3d7b3b0def0bc9fb72d93 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Tue, 2 Sep 2025 17:18:11 +1200
Subject: [PATCH 014/146] Add CBaseEntity:SetAbsVelocity Add SetPhysVelocity
---
scripts/vscripts/alyxlib/extensions/entity.lua | 12 ++++++++++++
scripts/vscripts/alyxlib/globals.lua | 8 ++++++++
2 files changed, 20 insertions(+)
diff --git a/scripts/vscripts/alyxlib/extensions/entity.lua b/scripts/vscripts/alyxlib/extensions/entity.lua
index 5f97d71..a3ea1c7 100644
--- a/scripts/vscripts/alyxlib/extensions/entity.lua
+++ b/scripts/vscripts/alyxlib/extensions/entity.lua
@@ -587,4 +587,16 @@ function CBaseEntity:ClearParent()
self:SetParent(nil, nil)
end
+---
+---Sets the absolute world velocity of the entity.
+---
+---@param velocity Vector The target velocity in units/second.
+function CBaseEntity:SetAbsVelocity(velocity)
+ local currentVelocity = GetPhysVelocity(self)
+
+ local impulse = velocity - currentVelocity
+
+ self:ApplyAbsVelocityImpulse(impulse)
+end
+
return version
\ No newline at end of file
diff --git a/scripts/vscripts/alyxlib/globals.lua b/scripts/vscripts/alyxlib/globals.lua
index c7acdaa..d02f37c 100644
--- a/scripts/vscripts/alyxlib/globals.lua
+++ b/scripts/vscripts/alyxlib/globals.lua
@@ -1071,4 +1071,12 @@ function CalcClosestCornerOnEntityAABB(entity, position)
return closestCorner
end
+---
+---Sets the absolute world velocity of an entity.
+---
+---@param velocity Vector The target velocity in units/second.
+function SetPhysVelocity(entity, velocity)
+ entity:SetAbsVelocity(velocity)
+end
+
return _version
\ No newline at end of file
From 0de9d28350c7885e91e10ab1df7860359595757c Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Tue, 2 Sep 2025 17:21:40 +1200
Subject: [PATCH 015/146] Fix noclipvr incorrect move type
---
scripts/vscripts/alyxlib/debug/vr.lua | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/scripts/vscripts/alyxlib/debug/vr.lua b/scripts/vscripts/alyxlib/debug/vr.lua
index 3942939..e6b864d 100644
--- a/scripts/vscripts/alyxlib/debug/vr.lua
+++ b/scripts/vscripts/alyxlib/debug/vr.lua
@@ -120,6 +120,9 @@ RegisterAlyxLibConvar("noclip_vr_enabled", "0", "True if noclip_vr is enabled (r
---Tracks initial button press to prevent repeated logic execution while held
local quickTurnFlag = false
+---Used to track user's movement type so it can be reset
+local movetype = Convars:GetInt('hlvr_movetype_default')
+
local function noclipVRThink()
-- Check offhand first because it's most common, then check primary hand movement
local moveVector = Player:GetAnalogActionPositionForHand(Player.SecondaryHand.Literal, ANALOG_INPUT_TELEPORT_TURN)
@@ -128,10 +131,9 @@ local function noclipVRThink()
end
if moveVector:Length() > 0 then
- local moveType = Player:GetMoveType()
local dir
- if moveType == PlayerMoveType.ContinuousHand then
+ if movetype == PlayerMoveType.ContinuousHand then
dir = (Player.SecondaryHand:GetAngles():Left() * moveVector.x) + (Player.SecondaryHand:GetAngles():Forward() * moveVector.y)
else
dir = (Player:EyeAngles():Left() * moveVector.x) + (Player:EyeAngles():Forward() * moveVector.y)
@@ -175,9 +177,6 @@ end
RegisterAlyxLibConvar("noclip_vr_speed", "2", "Speed of the VR noclip movement", 0)
RegisterAlyxLibConvar("noclip_vr_boost_speed", "5", "Speed of the VR noclip movement when holding trigger", 0)
----Used to track user's movement type so it can be reset
-local movetype = Convars:GetInt('hlvr_movetype_default')
-
RegisterAlyxLibCommand("noclip_vr", function (_, on)
local noclipVREnabled
if on == nil then
From 0539ddfd57f04809073c2f00be6af9898f022529 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Tue, 2 Sep 2025 17:25:38 +1200
Subject: [PATCH 016/146] Fix noclip VR inconsistently using secondary hand
---
scripts/vscripts/alyxlib/debug/vr.lua | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/scripts/vscripts/alyxlib/debug/vr.lua b/scripts/vscripts/alyxlib/debug/vr.lua
index e6b864d..f5cc1af 100644
--- a/scripts/vscripts/alyxlib/debug/vr.lua
+++ b/scripts/vscripts/alyxlib/debug/vr.lua
@@ -126,20 +126,22 @@ local movetype = Convars:GetInt('hlvr_movetype_default')
local function noclipVRThink()
-- Check offhand first because it's most common, then check primary hand movement
local moveVector = Player:GetAnalogActionPositionForHand(Player.SecondaryHand.Literal, ANALOG_INPUT_TELEPORT_TURN)
+ local hand = Player.SecondaryHand
if #moveVector == 0 then
moveVector = Player:GetAnalogActionPositionForHand(Player.PrimaryHand.Literal, ANALOG_INPUT_TELEPORT_TURN)
+ hand = Player.PrimaryHand
end
if moveVector:Length() > 0 then
local dir
if movetype == PlayerMoveType.ContinuousHand then
- dir = (Player.SecondaryHand:GetAngles():Left() * moveVector.x) + (Player.SecondaryHand:GetAngles():Forward() * moveVector.y)
+ dir = (hand:GetAngles():Left() * moveVector.x) + (hand:GetAngles():Forward() * moveVector.y)
else
dir = (Player:EyeAngles():Left() * moveVector.x) + (Player:EyeAngles():Forward() * moveVector.y)
end
- local velocity = dir * (Player:IsDigitalActionOnForHand(Player.SecondaryHand.Literal, 3) and Convars:GetFloat("noclip_vr_boost_speed") or Convars:GetFloat("noclip_vr_speed"))
+ local velocity = dir * (Player:IsDigitalActionOnForHand(hand.Literal, 3) and Convars:GetFloat("noclip_vr_boost_speed") or Convars:GetFloat("noclip_vr_speed"))
Player.HMDAnchor:SetOrigin(Player.HMDAnchor:GetOrigin() + velocity)
end
From 16f7475ed2c4fb8f04a331a4c9156743d2a27a7c Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Tue, 2 Sep 2025 19:09:52 +1200
Subject: [PATCH 017/146] Fix debug menu slider truncate default
---
panorama/scripts/custom_game/alyxlib_debug_menu.js | 10 +++++++---
scripts/vscripts/alyxlib/debug/debug_menu.lua | 4 ++--
2 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/panorama/scripts/custom_game/alyxlib_debug_menu.js b/panorama/scripts/custom_game/alyxlib_debug_menu.js
index 3ffdf5f..87a1d2f 100644
--- a/panorama/scripts/custom_game/alyxlib_debug_menu.js
+++ b/panorama/scripts/custom_game/alyxlib_debug_menu.js
@@ -445,7 +445,7 @@ class SubMenuSlider
this.text = text;
this.min = min;
this.max = max;
- this.isPercentage = false;
+ this.isPercentage = isPercentage;
this.callback = callback;
this.truncate = truncate;
@@ -523,8 +523,12 @@ class SubMenuSlider
if (this.isPercentage)
valueLabel.text = this.GetValueAsPercentage().toFixed(0);
- else
- valueLabel.text = this.value.toFixed(this.truncate);
+ else {
+ if (this.truncate > -1 && this.truncate < 20)
+ valueLabel.text = this.value.toFixed(this.truncate);
+ else
+ valueLabel.text = this.value;
+ }
}
/**
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index 95d0cb0..4143ee9 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -495,7 +495,7 @@ function DebugMenu:AddSlider(categoryId, sliderId, text, min, max, isPercentage,
max = max,
convar = convar,
isPercentage = isPercentage or false,
- truncate = truncate or -1,
+ truncate = truncate or 2,
increment = increment or 0
})
end
@@ -647,7 +647,7 @@ function DebugMenu:SendCategoryToPanel(category)
if default == nil then
default = Convars:GetFloat(item.convar) or item.min
end
- Panorama:Send(panel, "AddSlider", item.categoryId, item.id, item.text, item.convar, item.min, item.max, default, item.isPercentage, item.truncate, item.increment)
+ Panorama:Send(panel, "AddSlider", item.categoryId, item.id, item.text or item.convar, item.convar, item.min, item.max, default, item.isPercentage, item.truncate, item.increment)
elseif item.type == "cycle" then
-- Flatten values into an array of text
From 5fbdd8655bb8f1234acbb6cd075f2a59912c6114 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Mon, 8 Sep 2025 00:38:47 +1200
Subject: [PATCH 018/146] Add vecstr global Alias for Debug.SimpleVector
---
scripts/vscripts/alyxlib/debug/common.lua | 2 ++
1 file changed, 2 insertions(+)
diff --git a/scripts/vscripts/alyxlib/debug/common.lua b/scripts/vscripts/alyxlib/debug/common.lua
index 59e502e..5b96532 100644
--- a/scripts/vscripts/alyxlib/debug/common.lua
+++ b/scripts/vscripts/alyxlib/debug/common.lua
@@ -762,6 +762,8 @@ end
function Debug.SimpleVector(vector)
return "[" .. math.trunc(vector.x, 3) .. ", " .. math.trunc(vector.y, 3) .. ", " .. math.trunc(vector.z, 3) .. "]"
end
+---@diagnostic disable-next-line: lowercase-global
+vecstr = Debug.SimpleVector
---
---Draw a simple sphere without worrying about all the properties.
From 5183a69d8625f37e685200461b6a5220fa606e4f Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Mon, 8 Sep 2025 00:41:21 +1200
Subject: [PATCH 019/146] Add Debug.TableStr
---
scripts/vscripts/alyxlib/debug/common.lua | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/scripts/vscripts/alyxlib/debug/common.lua b/scripts/vscripts/alyxlib/debug/common.lua
index 5b96532..09fa13d 100644
--- a/scripts/vscripts/alyxlib/debug/common.lua
+++ b/scripts/vscripts/alyxlib/debug/common.lua
@@ -954,4 +954,18 @@ function Debug.Try(action, ...)
end
end
+---
+---Converts a table to single line string representation.
+---
+---@param tbl table # The table to convert.
+---@return string # The string representation.
+function Debug.TableStr(tbl)
+ local vals = {}
+ for key, value in pairs(tbl) do
+ table.insert(vals, key.." = "..tostring(value))
+ end
+
+ return "{ "..table.concat(vals, ", ") .. " }"
+end
+
return Debug.version
\ No newline at end of file
From df388f2ff06922766e8ea299b0d3345ca47df137 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Mon, 8 Sep 2025 00:42:30 +1200
Subject: [PATCH 020/146] Add RegisterAlyxLibEasyConvar Allows for callback and
persistence while listed under alyxlib
---
scripts/vscripts/alyxlib/debug/commands.lua | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/scripts/vscripts/alyxlib/debug/commands.lua b/scripts/vscripts/alyxlib/debug/commands.lua
index 6b2b6c2..9dccc24 100644
--- a/scripts/vscripts/alyxlib/debug/commands.lua
+++ b/scripts/vscripts/alyxlib/debug/commands.lua
@@ -37,6 +37,26 @@ function RegisterAlyxLibConvar(name, defaultValue, helpText, flags)
alyxlibCommands[name] = "Default: "..tostring(defaultValue)..", "..helpText
end
+---
+---Registers a new AlyxLib console variable.
+---
+---@param name string # Name of the convar that will be given in the console.
+---@param defaultValue string # Default value of the convar.
+---@param helpText? string # Description of the convar.
+---@param flags? integer # Flags for the convar.
+---@param postUpdate? fun(newVal:string, oldVal:string): any # Update function called after the value has been changed.
+---@param persistent? boolean # Whether the convar should be persistent.
+function RegisterAlyxLibEasyConvar(name, defaultValue, helpText, flags, postUpdate, persistent)
+ helpText = helpText or "[No description]"
+ EasyConvars:RegisterConvar(name, defaultValue, helpText, flags or 0, postUpdate)
+
+ if persistent then
+ EasyConvars:SetPersistent(name, true)
+ end
+
+ alyxlibCommands[name] = "Default: "..tostring(defaultValue)..", "..helpText
+end
+
Convars:RegisterCommand("alyxlib_commands", function (_, ...)
local maxNameLen = 0
for name in pairs(alyxlibCommands) do
From a27bcd0d1109025d9d9aba896860025b9c68b1c4 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Mon, 8 Sep 2025 00:44:05 +1200
Subject: [PATCH 021/146] Add print_ent_by_index
---
scripts/vscripts/alyxlib/debug/commands.lua | 25 +++++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/scripts/vscripts/alyxlib/debug/commands.lua b/scripts/vscripts/alyxlib/debug/commands.lua
index 9dccc24..048d0f1 100644
--- a/scripts/vscripts/alyxlib/debug/commands.lua
+++ b/scripts/vscripts/alyxlib/debug/commands.lua
@@ -384,6 +384,31 @@ RegisterAlyxLibCommand("print_ents", function (_, pattern, ...)
Debug.PrintEntities(pattern, false, false, properties)
end, "Prints all entities with class, name or model matching a pattern", 0)
+---
+---Prints an entity by its index.
+---
+RegisterAlyxLibCommand("print_ent_by_index", function(_, index)
+
+ if index == nil then
+ warn("Must supply an index to search for, e.g. prints_ent_by_index 1")
+ return
+ end
+
+ local i = tonumber(index)
+ if i == nil then
+ warn("Index must be a number, e.g. prints_ent_by_index 1")
+ return
+ end
+
+ local ent = EntIndexToHScript(i)
+ if ent == nil then
+ warn("Could not find entity with index '"..index.."'")
+ return
+ end
+
+ print(Debug.EntStr(ent))
+end, "Prints the entity with the given index", 0)
+
---
---Show the position of an entity relative to the player using debug drawing.
---
From 517c756c545a00a36177a160284ef834a85bba7f Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Mon, 8 Sep 2025 00:47:08 +1200
Subject: [PATCH 022/146] Overhaul weapon_switch detection
Player event now includes which hand equipped. The event is delayed to
let the weapon entity move into the hand.
---
scripts/vscripts/alyxlib/player/data.lua | 116 +++++++++++++--------
scripts/vscripts/alyxlib/player/events.lua | 28 ++---
2 files changed, 91 insertions(+), 53 deletions(-)
diff --git a/scripts/vscripts/alyxlib/player/data.lua b/scripts/vscripts/alyxlib/player/data.lua
index 689da23..4670060 100644
--- a/scripts/vscripts/alyxlib/player/data.lua
+++ b/scripts/vscripts/alyxlib/player/data.lua
@@ -58,13 +58,39 @@ local currentHandle = nil
local syncPaused = false
+---Standard local weapon offsets from hand when equipped
+local weaponOffsets = {
+ [0] = { -- left
+ hand_use_controller = Vector(0.027, 0.012, -0.124),
+ hlvr_weapon_energygun = Vector(-0.025, 0.048, 2.014),
+ hlvr_weapon_shotgun = Vector(0.402, 0.103, 1.652),
+ hlvr_weapon_rapidfire = Vector(0.392, 0.102, 1.658),
+ hlvr_multitool = Vector(3.079, 0.016, 1.209),
+ -- unsure if offsets will be different for custom models,
+ -- using hlvr_weapon_energygun offset
+ hlvr_weapon_generic_pistol = Vector(-0.025, 0.048, 2.014),
+ },
+ [1] = { -- right
+ hand_use_controller = Vector(0.026, -0.009, -0.125),
+ hlvr_weapon_energygun = Vector(-0.025, -0.042, 2.013),
+ hlvr_weapon_shotgun = Vector(0.402, -0.097, 1.651),
+ hlvr_weapon_rapidfire = Vector(0.392, -0.096, 1.657),
+ hlvr_multitool = Vector(3.079, -0.006, 1.208),
+ -- unsure if offsets will be different for custom models,
+ -- using hlvr_weapon_energygun offset
+ hlvr_weapon_generic_pistol = Vector(-0.025, -0.042, 2.013),
+ },
+}
+
---
---Sync the equipped weapon state of the player with the given weapon classname.
---
---If no classname is given, the weapon classname will be determined from the player's criteria.
---
---@param classname? string # The classname of the weapon to sync.
+---@param handle? EntityHandle # The entity handle of the weapon to sync.
---@return EntityHandle? weaponHandle # The entity handle of the weapon that was equipped.
+---@return CPropVRHand? handHandle # The entity handle of the hand that the weapon was equipped to.
local function SyncEquippedWeaponState(classname, handle)
if syncPaused then return end
@@ -83,54 +109,61 @@ local function SyncEquippedWeaponState(classname, handle)
Player.PreviouslyEquipped = Player.CurrentlyEquipped
- ---@type EntityHandle
+ ---@type CPropVRHand?
+ local handHandle = nil
+
+ ---@type EntityHandle?
local weaponHandle = nil
- if classname == "hand_use_controller" then
- Player.CurrentlyEquipped = PLAYER_WEAPON_HAND
- weaponHandle = Player.PrimaryHand
- currentHandle = nil
- else
- weaponHandle = handle or Entities:FindBestMatching("", classname, Player.PrimaryHand:GetPalmPosition(), 64)
- -- if weaponHandle then
- -- print("Setting new weapon for", classname, weaponHandle)
- -- Debug.Sphere(weaponHandle:GetOrigin(), 2, 10)
- -- end
-
- if not IsValidEntity(weaponHandle) then
- warn("Could not find weapon entity for equipped weapon: " .. classname)
+ -- Find the weapon and hand
+ if not handle then
+ local bestDistance = math.huge
+ local bestWeapon = nil
+ for h = 0, 1 do
+ local hand = h == 0 and Player.LeftHand or Player.RightHand
+ local offset = weaponOffsets[h][classname] or Vector()
+ for _, wpn in ipairs(Entities:FindAllByClassname(classname)) do
+ local dist = VectorDistance(offset, hand:TransformPointWorldToEntity(wpn:GetOrigin()))
+ if dist < bestDistance then
+ print('new best weapon', entstr(wpn), dist)
+ bestDistance = dist
+ bestWeapon = wpn
+ handHandle = hand
+ end
+ end
end
+ handle = bestWeapon
+ end
+
+ weaponHandle = handle
+
+ -- if weaponHandle then
+ -- print("Setting new weapon for hand", entstr(handHandle), classname, entstr(weaponHandle))
+ -- Debug.Sphere(weaponHandle:GetOrigin(), 2, 10)
+ -- end
+
+ if not IsValidEntity(weaponHandle) then
+ warn("Could not find weapon entity for equipped weapon: " .. classname)
+ end
- currentHandle = weaponHandle
+ currentHandle = weaponHandle
- if classname == "hlvr_weapon_energygun" then
+ if handHandle == Player.PrimaryHand then
+
+ if classname == "hand_use_controller" then
+ Player.CurrentlyEquipped = PLAYER_WEAPON_HAND
+ elseif classname == "hlvr_weapon_energygun" then
Player.CurrentlyEquipped = PLAYER_WEAPON_ENERGYGUN
- if not IsValidEntity(Player.Items.weapons.energygun) then
- Player.Items.weapons.energygun = weaponHandle
- else
- weaponHandle = Player.Items.weapons.energygun
- end
+ Player.Items.weapons.energygun = weaponHandle
elseif classname == "hlvr_weapon_rapidfire" then
Player.CurrentlyEquipped = PLAYER_WEAPON_RAPIDFIRE
- if not IsValidEntity(Player.Items.weapons.rapidfire) then
- Player.Items.weapons.rapidfire = weaponHandle
- else
- weaponHandle = Player.Items.weapons.rapidfire
- end
+ Player.Items.weapons.rapidfire = weaponHandle
elseif classname == "hlvr_weapon_shotgun" then
Player.CurrentlyEquipped = PLAYER_WEAPON_SHOTGUN
- if not IsValidEntity(Player.Items.weapons.shotgun) then
- Player.Items.weapons.shotgun = weaponHandle
- else
- weaponHandle = Player.Items.weapons.shotgun
- end
+ Player.Items.weapons.shotgun = weaponHandle
elseif classname == "hlvr_multitool" then
Player.CurrentlyEquipped = PLAYER_WEAPON_MULTITOOL
- if not IsValidEntity(Player.Items.weapons.multitool) then
- Player.Items.weapons.multitool = weaponHandle
- else
- weaponHandle = Player.Items.weapons.multitool
- end
+ Player.Items.weapons.multitool = weaponHandle
elseif classname == "hlvr_weapon_generic_pistol" then
Player.CurrentlyEquipped = PLAYER_WEAPON_GENERIC_PISTOL
Player.Items.weapons.generic_pistol = weaponHandle
@@ -140,18 +173,19 @@ local function SyncEquippedWeaponState(classname, handle)
table.insert(Player.Items.weapons.genericpistols, weaponHandle)
end
end
- end
- Player:UpdateWeaponsExistence()
+ Player:UpdateWeaponsExistence()
+
+ end
-- This event can fire before the player has a hand
- if Player.PrimaryHand then
- Player.PrimaryHand.ItemHeld = weaponHandle
+ if handHandle then
+ handHandle.ItemHeld = weaponHandle
end
savePlayerData()
- return weaponHandle
+ return weaponHandle, handHandle
end
---
diff --git a/scripts/vscripts/alyxlib/player/events.lua b/scripts/vscripts/alyxlib/player/events.lua
index 1f4fc6e..9e63e32 100644
--- a/scripts/vscripts/alyxlib/player/events.lua
+++ b/scripts/vscripts/alyxlib/player/events.lua
@@ -639,6 +639,7 @@ ListenToGameEvent("player_drop_resin_in_backpack", listenEventPlayerDropResinInB
---@class PlayerEventWeaponSwitch : GameEventWeaponSwitch
---@field item EntityHandle|nil # The handle of the weapon being switched to or nil if no weapon.
---@field item_class string # Classname of the entity that was switched to.
+---@field hand CPropVRHand # Hand that the entity was switched to.
---Track weapon equipped
---@param data GameEventWeaponSwitch
@@ -647,21 +648,24 @@ local function listenEventWeaponSwitch(data)
-- Debug.PrintTable(data)
-- print("\n")
- local weaponHandle = playerData.SyncEquippedWeaponState(data.item)
+ Player:Delay(function()
+ local weaponHandle, handHandle = playerData.SyncEquippedWeaponState(data.item)
- -- Nil return means sync in paused
- if not weaponHandle then return end
+ -- Nil return means sync in paused
+ if not weaponHandle then return end
- -- Hand entity isn't returned, so nil it
- if weaponHandle == Player.LeftHand or weaponHandle == Player.RightHand then
- weaponHandle = nil
- end
+ -- Hand entity isn't returned, so nil it
+ if weaponHandle:GetClassname() == "hand_use_controller" then
+ weaponHandle = nil
+ end
- -- Registered callback
- local newdata = vlua.clone(data)--[[@as PlayerEventWeaponSwitch]]
- newdata.item = weaponHandle
- newdata.item_class = data.item
- eventCallback(data.game_event_name, newdata)
+ -- Registered callback
+ local newdata = vlua.clone(data)--[[@as PlayerEventWeaponSwitch]]
+ newdata.item = weaponHandle
+ newdata.item_class = data.item
+ newdata.hand = handHandle
+ eventCallback(data.game_event_name, newdata)
+ end)
end
ListenToGameEvent("weapon_switch", listenEventWeaponSwitch, nil)
From 0f5cb1579973df1847f9b883a66aacf36c062032 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Mon, 8 Sep 2025 00:49:07 +1200
Subject: [PATCH 023/146] fix concatenation error
---
scripts/vscripts/alyxlib/debug/commands.lua | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/scripts/vscripts/alyxlib/debug/commands.lua b/scripts/vscripts/alyxlib/debug/commands.lua
index 048d0f1..6f26488 100644
--- a/scripts/vscripts/alyxlib/debug/commands.lua
+++ b/scripts/vscripts/alyxlib/debug/commands.lua
@@ -506,7 +506,7 @@ RegisterAlyxLibCommand("ent_find_by_address", function (_, tblpart, colon, hash)
Msg("\tParent: " .. (tostring(foundEnt:GetMoveParent() or "[none]")) .."\n")
Msg("\tModel: " .. foundEnt:GetModelName())
else
- Msg("Could not find any entity matching '" .. hash .. "'")
+ Msg("Could not find any entity matching '" .. tostring(hash) .. "'")
end
end, "Prints info for an entity by its table address", 0)
From adac734196e5a5a25697c5344c86b9d7f7b03af4 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Mon, 8 Sep 2025 00:50:22 +1200
Subject: [PATCH 024/146] various doc
---
scripts/vscripts/alyxlib/class.lua | 4 +++-
scripts/vscripts/alyxlib/extensions/entity.lua | 2 +-
scripts/vscripts/alyxlib/globals.lua | 4 +---
scripts/vscripts/game/gameinit.lua | 2 +-
4 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/scripts/vscripts/alyxlib/class.lua b/scripts/vscripts/alyxlib/class.lua
index c07cbbc..57c022a 100644
--- a/scripts/vscripts/alyxlib/class.lua
+++ b/scripts/vscripts/alyxlib/class.lua
@@ -611,7 +611,9 @@ function isinstance(ent, class)
return false
end
----Check if an entity is using the entity class system.
+---
+---Check if an entity is using the AlyxLib class system.
+---
---@param ent EntityHandle # Entity to check.
---@return boolean # True if `ent` is a class entity, false otherwise.
function IsClassEntity(ent)
diff --git a/scripts/vscripts/alyxlib/extensions/entity.lua b/scripts/vscripts/alyxlib/extensions/entity.lua
index a3ea1c7..d8454a4 100644
--- a/scripts/vscripts/alyxlib/extensions/entity.lua
+++ b/scripts/vscripts/alyxlib/extensions/entity.lua
@@ -533,7 +533,7 @@ end
---
---@param output string # The name of the output to redirect.
---@param func function # The function to redirect to.
----@param entity? EntityHandle # The entity to redirect to, defaults to this entity.
+---@param entity? EntityHandle # The entity to redirect to, defaults to the calling entity.
function CEntityInstance:RedirectOutputFunc(output, func, entity)
entity = entity or self
diff --git a/scripts/vscripts/alyxlib/globals.lua b/scripts/vscripts/alyxlib/globals.lua
index d02f37c..c2fbeae 100644
--- a/scripts/vscripts/alyxlib/globals.lua
+++ b/scripts/vscripts/alyxlib/globals.lua
@@ -717,10 +717,8 @@ end
---@return boolean
function TraceLineExt(parameters)
if IsEntity(parameters.ignore) then
- ---@diagnostic disable-next-line: inject-field
parameters.ignoreent = {parameters.ignore}
else
- ---@diagnostic disable-next-line: inject-field
parameters.ignoreent = parameters.ignore
parameters.ignore = nil
end
@@ -763,7 +761,7 @@ function TraceLineExt(parameters)
end
---
----Does a raytrace along a line until it hits or the world or reaches the end of the line.
+---Does a raytrace along a line until it hits the world or reaches the end of the line.
---
---@param parameters TraceTableLine
---@return TraceTableLine
diff --git a/scripts/vscripts/game/gameinit.lua b/scripts/vscripts/game/gameinit.lua
index ab12688..c23b0ee 100644
--- a/scripts/vscripts/game/gameinit.lua
+++ b/scripts/vscripts/game/gameinit.lua
@@ -50,7 +50,7 @@ if IsServer() then
addonTable = _G.SERVER_ADDONS
else
---
- ---All init scripts running on the server.
+ ---All init scripts running on the client.
---
---The key is the workshop ID of the addon, and the value is a boolean indicating if there is an init script for the addon.
---
From 9909923b7da24f499c5c5320d04f7f96b579ec3f Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Mon, 8 Sep 2025 01:08:55 +1200
Subject: [PATCH 025/146] Add CBaseEntity:TestAABBIntersect
---
.../vscripts/alyxlib/extensions/entity.lua | 21 +++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/scripts/vscripts/alyxlib/extensions/entity.lua b/scripts/vscripts/alyxlib/extensions/entity.lua
index d8454a4..085cdc6 100644
--- a/scripts/vscripts/alyxlib/extensions/entity.lua
+++ b/scripts/vscripts/alyxlib/extensions/entity.lua
@@ -599,4 +599,25 @@ function CBaseEntity:SetAbsVelocity(velocity)
self:ApplyAbsVelocityImpulse(impulse)
end
+---
+---Checks whether this entity's axis-aligned bounding box intersects
+---with another entity or an explicit Mins/Maxs bounds table.
+---
+---If `other` is an entity, its bounds are queried with [GetBounds](lua://CBaseEntity.GetBounds).
+---
+---@param other EntityHandle|{ Mins: Vector, Maxs: Vector } # Entity or bounds to test against.
+function CBaseEntity:TestAABBIntersect(other)
+ local selfBounds = self:GetBounds()
+
+ if IsEntity(other) then
+ other = other:GetBounds()
+ end
+
+ return (
+ selfBounds.Mins.x <= other.Maxs.x and selfBounds.Maxs.x >= other.Mins.x and
+ selfBounds.Mins.y <= other.Maxs.y and selfBounds.Maxs.y >= other.Mins.y and
+ selfBounds.Mins.z <= other.Maxs.z and selfBounds.Maxs.z >= other.Mins.z
+ )
+end
+
return version
\ No newline at end of file
From 6ef58c99f598aa3bd3767511e2180a766cebe3f7 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Wed, 10 Sep 2025 21:34:21 +1200
Subject: [PATCH 026/146] Improve weapon_switch search
---
scripts/vscripts/alyxlib/player/data.lua | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/scripts/vscripts/alyxlib/player/data.lua b/scripts/vscripts/alyxlib/player/data.lua
index 4670060..491d267 100644
--- a/scripts/vscripts/alyxlib/player/data.lua
+++ b/scripts/vscripts/alyxlib/player/data.lua
@@ -123,12 +123,13 @@ local function SyncEquippedWeaponState(classname, handle)
local hand = h == 0 and Player.LeftHand or Player.RightHand
local offset = weaponOffsets[h][classname] or Vector()
for _, wpn in ipairs(Entities:FindAllByClassname(classname)) do
- local dist = VectorDistance(offset, hand:TransformPointWorldToEntity(wpn:GetOrigin()))
- if dist < bestDistance then
- print('new best weapon', entstr(wpn), dist)
- bestDistance = dist
- bestWeapon = wpn
- handHandle = hand
+ if wpn ~= hand.ItemHeld then
+ local dist = VectorDistance(offset, hand:TransformPointWorldToEntity(wpn:GetOrigin()))
+ if dist < bestDistance then
+ bestDistance = dist
+ bestWeapon = wpn
+ handHandle = hand
+ end
end
end
end
From 5de287af88d273b57650d3ff79f5576b67ee517c Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Thu, 11 Sep 2025 00:37:11 +1200
Subject: [PATCH 027/146] Fix pano table flatten
---
scripts/vscripts/alyxlib/panorama/core.lua | 34 +++++++++++++++++-----
1 file changed, 27 insertions(+), 7 deletions(-)
diff --git a/scripts/vscripts/alyxlib/panorama/core.lua b/scripts/vscripts/alyxlib/panorama/core.lua
index 0aaec2b..fb179c7 100644
--- a/scripts/vscripts/alyxlib/panorama/core.lua
+++ b/scripts/vscripts/alyxlib/panorama/core.lua
@@ -40,6 +40,22 @@ function Panorama:InitPanel(panelEntity, customId)
DoEntFireByInstanceHandle(panelEntity, "AddCSSClass", id, 0, nil, nil)
end
+---Flattens a nested ordered table into a single list.
+---@param tbl table # The table to flatten
+---@param out? table # Optional table to flatten into
+---@return table # The flattened table or `out`
+local function flattenOrderedTable(tbl, out)
+ out = out or {}
+ for _, value in ipairs(tbl) do
+ if type(value) == "table" then
+ flattenOrderedTable(value, out)
+ else
+ table.insert(out, value)
+ end
+ end
+ return out
+end
+
---
---Send data to a panorama panel.
---
@@ -54,27 +70,31 @@ function Panorama:Send(panelEntity, ...)
end
local dataString = id .. "|"
- local data = {...}
+ local n = select("#", ...)
local flattenedData = {}
- -- Flatten nested tables into data
- for _, value in ipairs(data) do
- if type(value) == "table" then
- data = vlua.extend(flattenedData, value)
+ -- Flatten nested tables and convert to strings
+ for i = 1, n do
+ local value = select(i, ...)
+ if value == nil then
+ table.insert(flattenedData, "")
+ elseif type(value) == "table" then
+ flattenOrderedTable(value, flattenedData)
else
- table.insert(flattenedData, value)
+ table.insert(flattenedData, tostring(value))
end
end
local dataLength = #flattenedData
-- Put all values into a single pipe separated string
- for index, value in ipairs(data) do
+ for index, value in ipairs(flattenedData) do
dataString = dataString .. tostring(value)
if index < dataLength then dataString = dataString .. "|" end
end
dataString = FilterText(dataString)
+
-- print("Sending to pano:", dataString)
SendToConsole("@panorama_dispatch_event AddStyleToEachChild('"..dataString.."')")
end
From a41c1fbf468d08bf4f920b2ea3918c92e297325f Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Thu, 11 Sep 2025 00:38:30 +1200
Subject: [PATCH 028/146] Add title to debug menu cycle
---
.../scripts/custom_game/alyxlib_debug_menu.js | 23 ++++++++++-----
scripts/vscripts/alyxlib/debug/debug_menu.lua | 29 ++++++++++++++-----
2 files changed, 37 insertions(+), 15 deletions(-)
diff --git a/panorama/scripts/custom_game/alyxlib_debug_menu.js b/panorama/scripts/custom_game/alyxlib_debug_menu.js
index 87a1d2f..91face8 100644
--- a/panorama/scripts/custom_game/alyxlib_debug_menu.js
+++ b/panorama/scripts/custom_game/alyxlib_debug_menu.js
@@ -285,12 +285,13 @@ class Category
* Adds a new value cycler to this category.
* @param {string} id String id for this cycle.
* @param {string} convar Convar to tie this cycle to (currently unsued in JS).
+ * @param {string} title Title for this cycle.
* @param {SubMenuCycleItem[]} values Text/value pairs for this cycle.
* @param {string?} selectedValue Starting selected value.
*/
- AddCycle(id, convar, values, selectedValue)
+ AddCycle(id, convar, title, values, selectedValue)
{
- let cycle = new SubMenuCycle(`${this.id}_${id}`, convar, values, (index) => {
+ let cycle = new SubMenuCycle(`${this.id}_${id}`, convar, title, values, (index) => {
FireOutput("_DebugMenuCallbackCycle", id, index + 1);
});
cycle.AddToPanel(this.content);
@@ -558,13 +559,15 @@ class SubMenuCycle
*
* @param {string} id
* @param {string} convar
+ * @param {string} title Text show next to each value
* @param {SubMenuCycleItem[]} values Maximum of 6 items
* @param {function} callback
* @param {number} selectedIndex
*/
- constructor(id, convar, values, callback, selectedIndex) {
+ constructor(id, convar, title, values, callback, selectedIndex) {
this.id = id;
this.convar = convar;
+ this.title = title;
this.values = values;//values.slice(0, 6);
this.callback = callback;
@@ -585,8 +588,11 @@ class SubMenuCycle
const col = CreatePanel("Panel", btnRight, null, "cycle_button_right_col");
for (let [index,item] of this.values.entries()) {
/**@type {Label} */
- const text = CreatePanel("Label", col, "item"+index, "cycle_label");
- text.text = item.text;
+ const label = CreatePanel("Label", col, "item"+index, "cycle_label");
+ if (this.title && this.title != "")
+ label.text = `${this.title} : ${item.text}`;
+ else
+ label.text = item.text;
}
CreatePanel("Label", col, "custom", "cycle_label").text = "Custom";
const dotRow = CreatePanel("Panel", col, null, "dot_row");
@@ -1084,8 +1090,9 @@ function ParseCommand(command, args)
const id = args[1];
const convar = args[2];
- const currentValue = args[3];
- const rawValues = args.slice(4);
+ const title = args[3];
+ const currentValue = args[4];
+ const rawValues = args.slice(5);
/**@type {SubMenuCycleItem[]} */
const values = [];
for (let i = 0; i < rawValues.length; i+=2) {
@@ -1095,7 +1102,7 @@ function ParseCommand(command, args)
});
}
- category.AddCycle(id, convar, values, currentValue);
+ category.AddCycle(id, convar, title, values, currentValue);
break;
case "setitemtext": {
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index 4143ee9..17bed00 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -507,10 +507,11 @@ end
---
---@param categoryId string # The id of the category to add this cycle to
---@param cycleId string # The unique id for this new cycle
----@param values {text:string,value:any}[] # List of text/value pairs for this cycle
+---@param title string|nil # The text to display next to each value
+---@param values {text:string,value:any}[]|string[] # List of text/value pairs for this cycle, or a list of values
---@param command string|fun(index:number, item:{text:string,value:any?}, cycle:DebugMenuItem) # Convar name or function callback
---@param defaultValue? any|fun():any # Value for this cycle to start with
-function DebugMenu:AddCycle(categoryId, cycleId, values, command, defaultValue)
+function DebugMenu:AddCycle(categoryId, cycleId, title, values, command, defaultValue)
local category = self:GetCategory(categoryId)
if not category then
warn("Cannot add toggle '"..cycleId.."': Category '"..categoryId.."' does not exist!")
@@ -521,8 +522,17 @@ function DebugMenu:AddCycle(categoryId, cycleId, values, command, defaultValue)
error("Cycle values must be a table with at least 1 value", 2)
end
- for k,v in ipairs(values) do
- v.value = tostring(v.value or (k - 1))
+ ---@type {text:string,value:any}
+ local parsedValues = {}
+
+ if type(values[1]) == "string" then
+ for k,v in ipairs(values) do
+ table.insert(parsedValues, {text = v, value = k - 1})
+ end
+ else
+ for k,v in ipairs(values) do
+ table.insert(parsedValues, {text = v.text, value = v.value or (k - 1)})
+ end
end
local callback
@@ -541,6 +551,10 @@ function DebugMenu:AddCycle(categoryId, cycleId, values, command, defaultValue)
end
elseif type(command) == "function" then
callback = command
+
+ if defaultValue == nil then
+ defaultValue = parsedValues[1].value
+ end
end
table.insert(category.items, {
@@ -548,9 +562,10 @@ function DebugMenu:AddCycle(categoryId, cycleId, values, command, defaultValue)
id = cycleId,
callback = callback,
type = "cycle",
- values = values,
+ values = parsedValues,
default = defaultValue,
- convar = convar
+ convar = convar,
+ text = title
})
end
@@ -665,7 +680,7 @@ function DebugMenu:SendCategoryToPanel(category)
default = Convars:GetStr(item.convar)
end
- Panorama:Send(panel, "AddCycle", item.categoryId, item.id, item.convar, default, values)
+ Panorama:Send(panel, "AddCycle", item.categoryId, item.id, item.convar, item.text, default, values)
else
warn("Unknown item type '"..item.type.."'")
end
From aeb02efe383350fd19edd0f0a07749e657203467 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Sat, 13 Sep 2025 01:44:31 +1200
Subject: [PATCH 029/146] Add EasyConvars:Exists
---
scripts/vscripts/alyxlib/helpers/easyconvars.lua | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/scripts/vscripts/alyxlib/helpers/easyconvars.lua b/scripts/vscripts/alyxlib/helpers/easyconvars.lua
index ae7d0ac..a03269f 100644
--- a/scripts/vscripts/alyxlib/helpers/easyconvars.lua
+++ b/scripts/vscripts/alyxlib/helpers/easyconvars.lua
@@ -372,6 +372,15 @@ function EasyConvars:GetConvarData(name)
return self.registered[name]
end
+---
+---Checks if an easy convar exists.
+---
+---@param name string # Name of the convar
+---@return boolean # Returns true if the convar exists
+function EasyConvars:Exists(name)
+ return self.registered[name] ~= nil
+end
+
---
---Returns the convar as a string.
---
From 27a68fb90f7cc497302427488a3e59a7a99fa944 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Sat, 13 Sep 2025 01:46:39 +1200
Subject: [PATCH 030/146] Add math.obb file
Functions for testing and drawing OBB and AABB intersections
---
.../vscripts/alyxlib/extensions/entity.lua | 49 +++-
scripts/vscripts/alyxlib/init.lua | 1 +
scripts/vscripts/alyxlib/math/obb.lua | 243 ++++++++++++++++++
3 files changed, 279 insertions(+), 14 deletions(-)
create mode 100644 scripts/vscripts/alyxlib/math/obb.lua
diff --git a/scripts/vscripts/alyxlib/extensions/entity.lua b/scripts/vscripts/alyxlib/extensions/entity.lua
index 085cdc6..f60e11d 100644
--- a/scripts/vscripts/alyxlib/extensions/entity.lua
+++ b/scripts/vscripts/alyxlib/extensions/entity.lua
@@ -600,24 +600,45 @@ function CBaseEntity:SetAbsVelocity(velocity)
end
---
----Checks whether this entity's axis-aligned bounding box intersects
----with another entity or an explicit Mins/Maxs bounds table.
+---Tests if the OBB of this entity intersects with the OBB of another entity.
+---
+---@param other EntityHandle # The other entity.
+---@return boolean # True if the OBB of this entity intersects with the OBB of another entity.
+function CBaseEntity:OBBvsOBB(other)
+ return OBBvsOBB(
+ GetEntityOBBData(self), self:GetOrigin(), self:GetAngles(),
+ GetEntityOBBData(other), other:GetOrigin(), other:GetAngles()
+ )
+end
+
---
----If `other` is an entity, its bounds are queried with [GetBounds](lua://CBaseEntity.GetBounds).
+---Tests if the AABB of this entity intersects with the OBB of another entity.
---
----@param other EntityHandle|{ Mins: Vector, Maxs: Vector } # Entity or bounds to test against.
-function CBaseEntity:TestAABBIntersect(other)
- local selfBounds = self:GetBounds()
-
- if IsEntity(other) then
- other = other:GetBounds()
- end
+---The AABB is defined by the entity's bounding mins/maxs and its current origin/angles.
+---
+---@param other EntityHandle # The other entity.
+---@return boolean # True if the AABB of this entity intersects with the OBB of another entity.
+function CBaseEntity:AABBvsOBB(other)
+ local aMin, aMax = GetEntityAABB(self)
- return (
- selfBounds.Mins.x <= other.Maxs.x and selfBounds.Maxs.x >= other.Mins.x and
- selfBounds.Mins.y <= other.Maxs.y and selfBounds.Maxs.y >= other.Mins.y and
- selfBounds.Mins.z <= other.Maxs.z and selfBounds.Maxs.z >= other.Mins.z
+ return AABBvsOBB(
+ aMin, aMax,
+ GetEntityOBBData(other), other:GetOrigin(), other:GetAngles()
)
end
+---
+---Tests if the AABB of this entity intersects with the AABB of another entity.
+---
+---The AABB is defined by the entity's bounding mins/maxs and its current origin/angles.
+---
+---@param other EntityHandle # The other entity.
+---@return boolean # True if the AABB of this entity intersects with the AABB of another entity.
+function CBaseEntity:AABBvsAABB(other)
+ local aMin, aMax = GetEntityAABB(self)
+ local bMin, bMax = GetEntityAABB(other)
+ return AABBvsAABB(aMin, aMax, bMin, bMax)
+end
+
+
return version
\ No newline at end of file
diff --git a/scripts/vscripts/alyxlib/init.lua b/scripts/vscripts/alyxlib/init.lua
index 9b032fb..50277fc 100644
--- a/scripts/vscripts/alyxlib/init.lua
+++ b/scripts/vscripts/alyxlib/init.lua
@@ -61,6 +61,7 @@ alyxlib_require "alyxlib.extensions.npc"
alyxlib_require "alyxlib.extensions.template"
alyxlib_require "alyxlib.math.common"
alyxlib_require "alyxlib.math.weighted_random"
+alyxlib_require "alyxlib.math.obb"
alyxlib_require "alyxlib.data.queue"
alyxlib_require "alyxlib.data.stack"
alyxlib_require "alyxlib.data.inventory"
diff --git a/scripts/vscripts/alyxlib/math/obb.lua b/scripts/vscripts/alyxlib/math/obb.lua
new file mode 100644
index 0000000..f3d19ae
--- /dev/null
+++ b/scripts/vscripts/alyxlib/math/obb.lua
@@ -0,0 +1,243 @@
+--[[
+ v1.0.0
+ https://github.com/FrostSource/alyxlib
+
+ Functions for drawing and testing OBB intersections.
+
+ If not using `vscripts/alyxlib/init.lua`, load this file at game start using the following line:
+
+ require "alyxlib.math.obb"
+]]
+
+local version = "v1.0.0"
+
+---
+---Projects an OBB onto an axis.
+---
+---@param centerLocal Vector # The center of the OBB in local space.
+---@param half Vector # The half extents of the OBB in local space.
+---@param origin Vector # The origin of the OBB in world space.
+---@param angles QAngle # The angles of the OBB in world space.
+---@param axis Vector # The axis to project on.
+local function projectOBB(centerLocal, half, origin, angles, axis)
+ local f, r, u = angles:Forward(), angles:Left(), angles:Up()
+ local centerWorld = origin + f*centerLocal.x + r*centerLocal.y + u*centerLocal.z
+ local radius = half.x*math.abs(f:Dot(axis)) +
+ half.y*math.abs(r:Dot(axis)) +
+ half.z*math.abs(u:Dot(axis))
+ return centerWorld:Dot(axis) - radius, centerWorld:Dot(axis) + radius
+end
+
+---
+---OBB data table for use with obb.lua functions.
+---
+---@class OBBData
+---@field center Vector # The center of the OBB in local space.
+---@field half Vector # The half extents of the OBB in local space.
+
+---
+---Returns the center and half extents of an entity's OBB in local space.
+---
+---@param entity EntityHandle # The entity.
+---@return OBBData # The OBB data.
+function GetEntityOBBData(entity)
+ local mins = entity:GetBoundingMins()
+ local maxs = entity:GetBoundingMaxs()
+ local center = (mins + maxs) * 0.5
+ local half = (maxs - mins) * 0.5
+ return {
+ center = center,
+ half = half
+ }
+end
+
+---
+---Returns the world space minimum and maximum corners of an entity's OBB.
+---
+---@param entity EntityHandle # The entity.
+---@return Vector # The world space minimum corner.
+---@return Vector # The world space maximum corner.
+function GetEntityAABB(entity)
+ local data = GetEntityOBBData(entity)
+ local origin = entity:GetOrigin()
+ local ang = entity:GetAngles()
+
+ local f, r, u = ang:Forward(), ang:Left(), ang:Up()
+
+ -- world center of OBB
+ local center = origin
+ + f * data.center.x
+ + r * data.center.y
+ + u * data.center.z
+
+ -- half extents projected onto world axes
+ local hx = math.abs(f.x) * data.half.x +
+ math.abs(r.x) * data.half.y +
+ math.abs(u.x) * data.half.z
+
+ local hy = math.abs(f.y) * data.half.x +
+ math.abs(r.y) * data.half.y +
+ math.abs(u.y) * data.half.z
+
+ local hz = math.abs(f.z) * data.half.x +
+ math.abs(r.z) * data.half.y +
+ math.abs(u.z) * data.half.z
+
+ local halfWorld = Vector(hx, hy, hz)
+
+ return center - halfWorld, center + halfWorld
+end
+
+---
+---Tests if two AABBs intersect.
+---
+---@param aMin Vector # The minimum corner of the first AABB.
+---@param aMax Vector # The maximum corner of the first AABB.
+---@param bMin Vector # The minimum corner of the second AABB.
+---@param bMax Vector # The maximum corner of the second AABB.
+---@return boolean # True if the AABBs intersect.
+function AABBvsAABB(aMin, aMax, bMin, bMax)
+ return aMin.x <= bMax.x and aMax.x >= bMin.x and
+ aMin.y <= bMax.y and aMax.y >= bMin.y and
+ aMin.z <= bMax.z and aMax.z >= bMin.z
+end
+
+---
+---Tests if two OBBs intersect.
+---
+---@param obbDataA OBBData # The data of the first OBB.
+---@param originA Vector # The world space origin of the first OBB.
+---@param anglesA QAngle # The angles of the first OBB.
+---@param obbDataB OBBData # The data of the second OBB.
+---@param originB Vector # The world space origin of the second OBB.
+---@param anglesB QAngle # The angles of the second OBB.
+---@return boolean # True if the OBBs intersect.
+function OBBvsOBB(obbDataA, originA, anglesA, obbDataB, originB, anglesB)
+ local fA, rA, uA = anglesA:Forward(), anglesA:Left(), anglesA:Up()
+ local fB, rB, uB = anglesB:Forward(), anglesB:Left(), anglesB:Up()
+
+ local axes = {fA, rA, uA, fB, rB, uB}
+
+ for _,a in ipairs({fA, rA, uA}) do
+ for _,b in ipairs({fB, rB, uB}) do
+ local cross = a:Cross(b)
+ if cross:Length() > 0.0001 then
+ table.insert(axes, cross:Normalized())
+ end
+ end
+ end
+
+ for _,axis in ipairs(axes) do
+ local minA,maxA = projectOBB(obbDataA.center, obbDataA.half, originA, anglesA, axis)
+ local minB,maxB = projectOBB(obbDataB.center, obbDataB.half, originB, anglesB, axis)
+ if minA > maxB or minB > maxA then
+ return false
+ end
+ end
+
+ return true
+end
+
+---
+---Tests if an AABB and an OBB intersect.
+---
+---@param aabbMin Vector # The minimum corner of the AABB.
+---@param aabbMax Vector # The maximum corner of the AABB.
+---@param obbData OBBData # The data of the OBB.
+---@param obbOrigin Vector # The world space origin of the OBB.
+---@param obbAngles QAngle # The angles of the OBB.
+---@return boolean # True if the AABB and OBB intersect.
+function AABBvsOBB(aabbMin, aabbMax, obbData, obbOrigin, obbAngles)
+
+ local aCenter = (aabbMin + aabbMax) * 0.5
+ local aOBB = {
+ center = Vector(0,0,0),
+ half = (aabbMax - aabbMin) * 0.5
+ }
+
+ local aAngles = QAngle(0,0,0)
+
+ return OBBvsOBB(
+ aOBB, aCenter, aAngles,
+ obbData, obbOrigin, obbAngles
+ )
+end
+
+---
+---Draws an OBB in the world.
+---
+---@param obbData OBBData # The data of the OBB.
+---@param origin Vector # The world space origin of the OBB.
+---@param angles QAngle # The angles of the OBB.
+---@param color Vector # The color of the OBB.
+---@param noDepthTest boolean # True if the OBB should be drawn above all geometry.
+---@param seconds number # The number of seconds the OBB should be visible for.
+function DebugDrawOBB(obbData, origin, angles, color, noDepthTest, seconds)
+
+ local f = angles:Forward()
+ local r = angles:Left()
+ local u = angles:Up()
+
+ -- world center of the OBB
+ local center = origin
+ + f * obbData.center.x
+ + r * obbData.center.y
+ + u * obbData.center.z
+
+ -- build corners
+ local corners = {}
+ local i = 1
+ for sx = -1, 1, 2 do
+ for sy = -1, 1, 2 do
+ for sz = -1, 1, 2 do
+ corners[i] = center
+ + f * (sx * obbData.half.x)
+ + r * (sy * obbData.half.y)
+ + u * (sz * obbData.half.z)
+ i = i + 1
+ end
+ end
+ end
+
+ -- edge indices
+ local edges = {
+ {1,2}, {2,4}, {4,3}, {3,1}, -- bottom
+ {5,6}, {6,8}, {8,7}, {7,5}, -- top
+ {1,5}, {2,6}, {3,7}, {4,8} -- verticals
+ }
+
+ -- draw lines
+ for _,e in ipairs(edges) do
+ DebugDrawLine(corners[e[1]], corners[e[2]],
+ color.x, color.y, color.z,
+ noDepthTest or false, seconds or 0.0)
+ end
+end
+
+---
+---Draws an entity's OBB in the world.
+---
+---@param entity EntityHandle # The entity to draw the OBB for.
+---@param color Vector # The color of the OBB in RGB.
+---@param noDepthTest boolean # True if the OBB should be drawn above all geometry.
+---@param seconds number # The number of seconds the OBB should be visible for.
+function DebugDrawEntityOBB(entity, color, noDepthTest, seconds)
+ local obbData = GetEntityOBBData(entity)
+ DebugDrawOBB(obbData, entity:GetOrigin(), entity:GetAngles(), color, noDepthTest, seconds)
+end
+
+---
+---Draws an entity's AABB in the world.
+---
+---The AABB is defined by the entity's bounding mins/maxs and its current origin/angles.
+---
+---@param entity EntityHandle # The entity to draw the AABB for.
+---@param color Vector # The color of the AABB in RGB.
+---@param noDepthTest boolean # True if the AABB should be drawn above all geometry.
+---@param seconds number # The number of seconds the AABB should be visible for.
+function DebugDrawEntityAABB(entity, color, noDepthTest, seconds)
+ local mins, maxs = GetEntityAABB(entity)
+ debugoverlay:Box(mins, maxs, color.x, color.y, color.z, 255, noDepthTest or false, seconds or 0)
+end
+
+return version
\ No newline at end of file
From b3d33a823f44b680331d22973c02d2a7f0a522e1 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Sat, 13 Sep 2025 02:37:07 +1200
Subject: [PATCH 031/146] No longer send cycle values to pano
Long pano strings cause issues and value don't need to be displayed
anyway
---
.../scripts/custom_game/alyxlib_debug_menu.js | 92 +++++++++----------
scripts/vscripts/alyxlib/debug/debug_menu.lua | 21 +++--
scripts/vscripts/alyxlib/globals.lua | 31 +++++++
3 files changed, 87 insertions(+), 57 deletions(-)
diff --git a/panorama/scripts/custom_game/alyxlib_debug_menu.js b/panorama/scripts/custom_game/alyxlib_debug_menu.js
index 91face8..d1d50a8 100644
--- a/panorama/scripts/custom_game/alyxlib_debug_menu.js
+++ b/panorama/scripts/custom_game/alyxlib_debug_menu.js
@@ -286,16 +286,16 @@ class Category
* @param {string} id String id for this cycle.
* @param {string} convar Convar to tie this cycle to (currently unsued in JS).
* @param {string} title Title for this cycle.
- * @param {SubMenuCycleItem[]} values Text/value pairs for this cycle.
- * @param {string?} selectedValue Starting selected value.
+ * @param {string[]} values Text values for this cycle.
+ * @param {number?} selectedIndex Starting selected index [0-n].
*/
- AddCycle(id, convar, title, values, selectedValue)
+ AddCycle(id, convar, title, values, selectedIndex)
{
let cycle = new SubMenuCycle(`${this.id}_${id}`, convar, title, values, (index) => {
FireOutput("_DebugMenuCallbackCycle", id, index + 1);
});
cycle.AddToPanel(this.content);
- cycle.SetSelectedValueNoFire(selectedValue);
+ cycle.SetSelectedIndexNoFire(selectedIndex);
this.items.push(cycle);
}
}
@@ -547,11 +547,11 @@ class SubMenuSlider
}
}
-/**
- * @typedef {Object} SubMenuCycleItem
- * @property {string} text
- * @property {string?} value
- */
+// /**
+// * @typedef {Object} SubMenuCycleItem
+// * @property {string} text
+// * @property {string?} value
+// */
class SubMenuCycle
{
@@ -560,7 +560,7 @@ class SubMenuCycle
* @param {string} id
* @param {string} convar
* @param {string} title Text show next to each value
- * @param {SubMenuCycleItem[]} values Maximum of 6 items
+ * @param {string[]} values Maximum of 6 items
* @param {function} callback
* @param {number} selectedIndex
*/
@@ -568,7 +568,7 @@ class SubMenuCycle
this.id = id;
this.convar = convar;
this.title = title;
- this.values = values;//values.slice(0, 6);
+ this.values = values;
this.callback = callback;
/** @type {Panel} */
@@ -590,9 +590,9 @@ class SubMenuCycle
/**@type {Label} */
const label = CreatePanel("Label", col, "item"+index, "cycle_label");
if (this.title && this.title != "")
- label.text = `${this.title} : ${item.text}`;
+ label.text = `${this.title} : ${item}`;
else
- label.text = item.text;
+ label.text = item;
}
CreatePanel("Label", col, "custom", "cycle_label").text = "Custom";
const dotRow = CreatePanel("Panel", col, null, "dot_row");
@@ -626,37 +626,18 @@ class SubMenuCycle
/**
* Sets the selected option without firing the callback.
- * @param {number} index The index of the option to select, starting from 0.
+ * @param {number?} index The index of the option to select, starting from 0.
*/
SetSelectedIndexNoFire(index) {
- index = (index + this.values.length) % this.values.length;
+ let foundValue = false;
- for (let i = 0; i < this.values.length; i++) {
- if (i == index) {
- this.SetSelectedValueNoFire(this.values[i].value);
- return;
- }
- }
- }
+ if (typeof index === "number" && Number.isFinite(index))
+ index = (index + this.values.length) % this.values.length;
- /**
- * Sets the selected option.
- * @param {number} index The index of the option to select, starting from 0.
- */
- SetSelectedIndex(index) {
- this.SetSelectedIndexNoFire(index);
- this.callback(this.selectedIndex);
- }
-
- SetSelectedValueNoFire(value) {
- let foundValue = false;
-
- // Find the matching value index
for (let i = 0; i < this.values.length; i++) {
- const _value = this.values[i].value;
const item = this.panel.FindChildTraverse("item" + i);
const dot = this.panel.FindChildTraverse("dot" + ((this.values.length-1) - i));
- if (_value === value) {
+ if (i == index) {
item.visible = true;
dot.SetHasClass("cycle_dots_selected", true);
foundValue = true;
@@ -673,11 +654,34 @@ class SubMenuCycle
custom.visible = false;
} else {
this.selectedIndex = -1;
- custom.text = `Custom (${value})`;
+ // custom.text = `Custom (${value})`;
custom.visible = true;
}
}
+ /**
+ * Sets the selected option.
+ * @param {number} index The index of the option to select, starting from 0.
+ */
+ SetSelectedIndex(index) {
+ this.SetSelectedIndexNoFire(index);
+ this.callback(this.selectedIndex);
+ }
+
+ SetSelectedValueNoFire(value) {
+ // Find the matching value index
+ for (let i = 0; i < this.values.length; i++) {
+ const _value = this.values[i];
+ if (_value === value) {
+ this.SetSelectedIndexNoFire(i);
+ return;
+ }
+ }
+
+ // Custom value
+ this.SetSelectedIndexNoFire(undefined);
+ }
+
SetSelectedValue(value) {
this.SetSelectedValueNoFire(value);
this.callback(this.selectedIndex);
@@ -1091,18 +1095,10 @@ function ParseCommand(command, args)
const id = args[1];
const convar = args[2];
const title = args[3];
- const currentValue = args[4];
+ const currentIndex = parseInt(args[4])-1;
const rawValues = args.slice(5);
- /**@type {SubMenuCycleItem[]} */
- const values = [];
- for (let i = 0; i < rawValues.length; i+=2) {
- values.push({
- text: rawValues[i],
- value: rawValues[i+1]
- });
- }
- category.AddCycle(id, convar, title, values, currentValue);
+ category.AddCycle(id, convar, title, rawValues, currentIndex);
break;
case "setitemtext": {
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index 17bed00..9373df5 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -665,22 +665,25 @@ function DebugMenu:SendCategoryToPanel(category)
Panorama:Send(panel, "AddSlider", item.categoryId, item.id, item.text or item.convar, item.convar, item.min, item.max, default, item.isPercentage, item.truncate, item.increment)
elseif item.type == "cycle" then
- -- Flatten values into an array of text
- local values = {}
- local index = 1
- for i = 1, #item.values do
- values[index] = item.values[i].text
- values[index+1] = item.values[i].value or (i - 1)
- index = index + 2
- end
local default = resolveDefault(item.default)
+
-- Use convar value if default isn't set
if default == nil and item.convar ~= "" then
default = Convars:GetStr(item.convar)
end
- Panorama:Send(panel, "AddCycle", item.categoryId, item.id, item.convar, item.text, default, values)
+ if default ~= nil then
+ -- find the index of the default value
+ local index = TableFindIndex(item.values, function(v) return v.value == default end)
+ if index > 0 then
+ default = index
+ else
+ default = nil
+ end
+ end
+
+ Panorama:Send(panel, "AddCycle", item.categoryId, item.id, item.convar, item.text, default, TablePluck(item.values, "text"))
else
warn("Unknown item type '"..item.type.."'")
end
diff --git a/scripts/vscripts/alyxlib/globals.lua b/scripts/vscripts/alyxlib/globals.lua
index c2fbeae..6bd0c38 100644
--- a/scripts/vscripts/alyxlib/globals.lua
+++ b/scripts/vscripts/alyxlib/globals.lua
@@ -580,6 +580,37 @@ function TableSize(tbl)
return size
end
+---
+---Collects all values for a specific key from a list of tables.
+---
+---@param tbl table[] # List of tables.
+---@param key any # Key to get values from.
+---@return any[] # List of values found for the key.
+function TablePluck(tbl, key)
+ local out = {}
+ for _, v in ipairs(tbl) do
+ if type(v) == "table" and v[key] ~= nil then
+ table.insert(out, v[key])
+ end
+ end
+ return out
+end
+
+---
+---Returns the index of the first value that matches the predicate.
+---
+---@param list table
+---@param predicate fun(value:any):boolean
+---@return integer
+function TableFindIndex(list, predicate)
+ for i, v in ipairs(list) do
+ if predicate(v) then
+ return i
+ end
+ end
+ return 0
+end
+
---
---Returns a random value from an array.
---
From 19e4f7362a3e49ee2444f62d0ade3e0f41626420 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Sun, 14 Sep 2025 22:35:23 +1200
Subject: [PATCH 032/146] add debug menu to ignore template
---
templates/gitignore.txt | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/templates/gitignore.txt b/templates/gitignore.txt
index e736d30..0f391a8 100644
--- a/templates/gitignore.txt
+++ b/templates/gitignore.txt
@@ -11,4 +11,8 @@ scripts/vscripts/game/gameinit.lua
.vscode/alyxlib.code-snippets
.vscode/vlua_snippets.code-snippets
panorama/scripts/custom_game/panorama_lua.js
-panorama/scripts/custom_game/panoramadoc.js
\ No newline at end of file
+panorama/scripts/custom_game/panoramadoc.js
+
+panorama/layout/custom_game/alyxlib_debug_menu.xml
+panorama/scripts/custom_game/alyxlib_debug_menu.js
+panorama/styles/custom_game/alyxlib_debug_menu.css
\ No newline at end of file
From 5958e56f081c98cdd6ebb177c773a3fc86c05108 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Sun, 14 Sep 2025 22:37:49 +1200
Subject: [PATCH 033/146] check sync paused in weapon_switch
---
scripts/vscripts/alyxlib/player/data.lua | 8 ++++++
scripts/vscripts/alyxlib/player/events.lua | 32 ++++++++++++----------
2 files changed, 25 insertions(+), 15 deletions(-)
diff --git a/scripts/vscripts/alyxlib/player/data.lua b/scripts/vscripts/alyxlib/player/data.lua
index 491d267..5b48384 100644
--- a/scripts/vscripts/alyxlib/player/data.lua
+++ b/scripts/vscripts/alyxlib/player/data.lua
@@ -219,12 +219,20 @@ local function ResumeWeaponStateSync()
syncPaused = false
end
+---
+---Check if weapon state synching is paused.
+---
+local function WeaponStateSyncPaused()
+ return syncPaused
+end
+
return {
version = version,
SyncEquippedWeaponState = SyncEquippedWeaponState,
RestorePreviouslyEquippedWeaponState = RestorePreviouslyEquippedWeaponState,
PauseWeaponStateSync = PauseWeaponStateSync,
ResumeWeaponStateSync = ResumeWeaponStateSync,
+ WeaponStateSyncPaused = WeaponStateSyncPaused,
SavePlayerData = savePlayerData,
LoadPlayerData = loadPlayerData
}
\ No newline at end of file
diff --git a/scripts/vscripts/alyxlib/player/events.lua b/scripts/vscripts/alyxlib/player/events.lua
index 9e63e32..3458693 100644
--- a/scripts/vscripts/alyxlib/player/events.lua
+++ b/scripts/vscripts/alyxlib/player/events.lua
@@ -648,24 +648,26 @@ local function listenEventWeaponSwitch(data)
-- Debug.PrintTable(data)
-- print("\n")
- Player:Delay(function()
- local weaponHandle, handHandle = playerData.SyncEquippedWeaponState(data.item)
+ if not playerData.WeaponStateSyncPaused() then
+ Player:Delay(function()
+ local weaponHandle, handHandle = playerData.SyncEquippedWeaponState(data.item)
- -- Nil return means sync in paused
- if not weaponHandle then return end
+ -- Nil return means sync in paused
+ if not weaponHandle then return end
- -- Hand entity isn't returned, so nil it
- if weaponHandle:GetClassname() == "hand_use_controller" then
- weaponHandle = nil
- end
+ -- Hand entity isn't returned, so nil it
+ if weaponHandle:GetClassname() == "hand_use_controller" then
+ weaponHandle = nil
+ end
- -- Registered callback
- local newdata = vlua.clone(data)--[[@as PlayerEventWeaponSwitch]]
- newdata.item = weaponHandle
- newdata.item_class = data.item
- newdata.hand = handHandle
- eventCallback(data.game_event_name, newdata)
- end)
+ -- Registered callback
+ local newdata = vlua.clone(data)--[[@as PlayerEventWeaponSwitch]]
+ newdata.item = weaponHandle
+ newdata.item_class = data.item
+ newdata.hand = handHandle
+ eventCallback(data.game_event_name, newdata)
+ end, 0.0)
+ end
end
ListenToGameEvent("weapon_switch", listenEventWeaponSwitch, nil)
From 2380bf2d72568ad79920f96d85424675cc4600c1 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Sun, 14 Sep 2025 22:39:05 +1200
Subject: [PATCH 034/146] add GetDefaultWeaponOffset
---
scripts/vscripts/alyxlib/player/data.lua | 18 +++++++++++++++++-
1 file changed, 17 insertions(+), 1 deletion(-)
diff --git a/scripts/vscripts/alyxlib/player/data.lua b/scripts/vscripts/alyxlib/player/data.lua
index 5b48384..de4b60e 100644
--- a/scripts/vscripts/alyxlib/player/data.lua
+++ b/scripts/vscripts/alyxlib/player/data.lua
@@ -82,6 +82,21 @@ local weaponOffsets = {
},
}
+---
+---Returns the default weapon offset for the given weapon classname and hand.
+---
+---@param classname string # The classname of the weapon
+---@param hand CPropVRHand|number # The hand to get the offset for
+---@return Vector # The default weapon offset, or `Vector()` if not found
+local function GetDefaultWeaponOffset(classname, hand)
+ hand = hand or Player.PrimaryHand:GetHandID()
+ if IsEntity(hand) then
+ hand = hand:GetHandID()
+ end
+
+ return weaponOffsets[hand][classname] or Vector()
+end
+
---
---Sync the equipped weapon state of the player with the given weapon classname.
---
@@ -234,5 +249,6 @@ return {
ResumeWeaponStateSync = ResumeWeaponStateSync,
WeaponStateSyncPaused = WeaponStateSyncPaused,
SavePlayerData = savePlayerData,
- LoadPlayerData = loadPlayerData
+ LoadPlayerData = loadPlayerData,
+ GetDefaultWeaponOffset = GetDefaultWeaponOffset
}
\ No newline at end of file
From 0b6b83cfa58f976d407a3414ef2317b8972381f7 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Sun, 14 Sep 2025 22:42:21 +1200
Subject: [PATCH 035/146] add debug menu to manifest type is now hardlink
because symlink does not compile
---
deployment_manifest.json | 26 ++++++++++++++++++++++----
1 file changed, 22 insertions(+), 4 deletions(-)
diff --git a/deployment_manifest.json b/deployment_manifest.json
index fe064bb..0e173ca 100644
--- a/deployment_manifest.json
+++ b/deployment_manifest.json
@@ -82,16 +82,34 @@
"panorama": [
{
- "type": "symlink",
- "description": "Panorama Lua integration symlink",
+ "type": "hardlink",
+ "description": "Panorama Lua integration hardlink",
"source": "{AlyxLib}/panorama/scripts/custom_game/panorama_lua.js",
"destination": "{AddonContent}/panorama/scripts/custom_game/panorama_lua.js"
},
{
- "type": "symlink",
- "description": "Panorama code completion symlink",
+ "type": "hardlink",
+ "description": "Panorama code completion hardlink",
"source": "{AlyxLib}/panorama/scripts/custom_game/panoramadoc.js",
"destination": "{AddonContent}/panorama/scripts/custom_game/panoramadoc.js"
+ },
+ {
+ "type": "hardlink",
+ "description": "DebugMenu layout hardlink",
+ "source": "{AlyxLib}/panorama/layout/custom_game/alyxlib_debug_menu.xml",
+ "destination": "{AddonContent}/panorama/layout/custom_game/alyxlib_debug_menu.xml"
+ },
+ {
+ "type": "hardlink",
+ "description": "DebugMenu script hardlink",
+ "source": "{AlyxLib}/panorama/scripts/custom_game/alyxlib_debug_menu.js",
+ "destination": "{AddonContent}/panorama/scripts/custom_game/alyxlib_debug_menu.js"
+ },
+ {
+ "type": "hardlink",
+ "description": "DebugMenu styles hardlink",
+ "source": "{AlyxLib}/panorama/styles/custom_game/alyxlib_debug_menu.css",
+ "destination": "{AddonContent}/panorama/styles/custom_game/alyxlib_debug_menu.css"
}
],
From 3b97a0c0bfd6f8d33f93c0a3b4a54fc9b5ad652d Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Sun, 14 Sep 2025 22:42:45 +1200
Subject: [PATCH 036/146] double scroll in debug menu for faster movement
---
panorama/scripts/custom_game/alyxlib_debug_menu.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/panorama/scripts/custom_game/alyxlib_debug_menu.js b/panorama/scripts/custom_game/alyxlib_debug_menu.js
index d1d50a8..50d8705 100644
--- a/panorama/scripts/custom_game/alyxlib_debug_menu.js
+++ b/panorama/scripts/custom_game/alyxlib_debug_menu.js
@@ -1169,6 +1169,7 @@ function ScrollHelperSchedule() {
return;
}
+ $.DispatchEvent(scrollHelperScheduleEvent, currentlySelectedCategory.panel);
$.DispatchEvent(scrollHelperScheduleEvent, currentlySelectedCategory.panel);
$.Schedule(scrollHelperSpeed, ScrollHelperSchedule);
}
From c0ecef4da1713a9d8c6d300b8aa2bf7b294fd187 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Sun, 14 Sep 2025 22:46:03 +1200
Subject: [PATCH 037/146] minor cleanup
---
scripts/vscripts/alyxlib/debug/debug_menu.lua | 4 ++--
scripts/vscripts/alyxlib/extensions/entity.lua | 2 +-
scripts/vscripts/alyxlib/player/data.lua | 3 ++-
3 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index 9373df5..de99626 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -773,8 +773,7 @@ function DebugMenu:StopListeningForMenuActivation()
end
if Convars:GetInt("developer") > 0 then
- local listenFunc = ListenToPlayerEvent or ListenToGameEvent
- listenFunc("vr_player_ready", function()
+ ListenToPlayerEvent("vr_player_ready", function()
-- Kill existing panel on load to avoid missing logic errors
local panel = Entities:FindByName(nil, "alyxlib_debug_menu")
if panel then
@@ -787,6 +786,7 @@ if Convars:GetInt("developer") > 0 then
end, nil)
end
+
--[[
Default AlyxLib tab
]]
diff --git a/scripts/vscripts/alyxlib/extensions/entity.lua b/scripts/vscripts/alyxlib/extensions/entity.lua
index f60e11d..f7ddc1b 100644
--- a/scripts/vscripts/alyxlib/extensions/entity.lua
+++ b/scripts/vscripts/alyxlib/extensions/entity.lua
@@ -484,7 +484,7 @@ function CBaseEntity:TrackProperty(propertyFunction, onChangeFunction, interval,
end
---
---- Untrack a property function which was set to be tracked using `CBaseEntity:TrackProperty`.
+---Untrack a property function which was set to be tracked using `CBaseEntity:TrackProperty`.
---
---@param propertyFunction fun(handle: EntityHandle): any # Property function to untrack, e.g. GetRenderAlpha.
function CBaseEntity:UntrackProperty(propertyFunction)
diff --git a/scripts/vscripts/alyxlib/player/data.lua b/scripts/vscripts/alyxlib/player/data.lua
index de4b60e..f282b73 100644
--- a/scripts/vscripts/alyxlib/player/data.lua
+++ b/scripts/vscripts/alyxlib/player/data.lua
@@ -155,7 +155,8 @@ local function SyncEquippedWeaponState(classname, handle)
-- if weaponHandle then
-- print("Setting new weapon for hand", entstr(handHandle), classname, entstr(weaponHandle))
- -- Debug.Sphere(weaponHandle:GetOrigin(), 2, 10)
+ -- debugoverlay:Sphere(weaponHandle:GetOrigin(), 2, 255, 255, 255, 255, true, 100)
+ -- debugoverlay:Text(weaponHandle:GetOrigin(), 0, entstr(weaponHandle).." : "..entstr(handHandle), 0, 255, 255, 255, 255, 100)
-- end
if not IsValidEntity(weaponHandle) then
From f0379fb9ef160b89705cd022232fef6dc33f5020 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Sun, 14 Sep 2025 22:47:09 +1200
Subject: [PATCH 038/146] add debug menu cfg commands
---
.../vscripts/alyxlib/debug/debug_menu_cfg.lua | 201 ++++++++++++++++++
scripts/vscripts/alyxlib/init.lua | 1 +
2 files changed, 202 insertions(+)
create mode 100644 scripts/vscripts/alyxlib/debug/debug_menu_cfg.lua
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu_cfg.lua b/scripts/vscripts/alyxlib/debug/debug_menu_cfg.lua
new file mode 100644
index 0000000..bf24d5f
--- /dev/null
+++ b/scripts/vscripts/alyxlib/debug/debug_menu_cfg.lua
@@ -0,0 +1,201 @@
+--[[
+ v1.0.0
+ https://github.com/FrostSource/alyxlib
+
+ Allows debug menu additions with a 'debug_menu.cfg' file or through the console.
+]]
+
+local version = "v1.0.0"
+
+local currentUserCategory = ""
+
+RegisterAlyxLibCommand("debug_menu_add_category", function (_, name)
+ currentUserCategory = DoUniqueString("category")
+ DebugMenu:AddCategory(currentUserCategory, name or "New Category")
+end, "Adds a new category to the debug menu")
+
+RegisterAlyxLibCommand("debug_menu_add_button", function (_, text, command)
+ DebugMenu:AddButton(currentUserCategory, DoUniqueString("button"), text or "Button", command or "echo Button pressed")
+end, "Adds a new button to the current category")
+
+RegisterAlyxLibCommand("debug_menu_add_toggle", function (_, text, commandOff, commandOn, default)
+ if default == nil then
+ if commandOn ~= nil and vlua.find({"off","on","0","1"}, string.lower(commandOn)) then
+ default = string.lower(commandOn)
+ commandOn = nil
+ end
+ else
+ default = string.lower(default)
+ end
+
+ default = (default == "on" or default == "1")
+
+ local command = commandOff
+
+ if commandOn ~= nil then
+ command = function(isOn)
+ if isOn then
+ SendToConsole(commandOn)
+ else
+ SendToConsole(commandOff)
+ end
+ end
+ end
+
+ DebugMenu:AddToggle(currentUserCategory, DoUniqueString("toggle"), text or "Toggle", command or "echo Toggle pressed", default)
+end, "Adds a new toggle to the current category")
+
+RegisterAlyxLibCommand("debug_menu_add_slider", function (_, text, command, min, max, ...)
+
+ local isPercentage = false
+ local truncate = nil
+ local increment = nil
+ local default = nil
+
+ for i = 1, select("#", ...) do
+ local arg = select(i, ...)
+ if arg == "%" then isPercentage = true
+ else
+ local prefix = arg:sub(1,1)
+ local value = tonumber(arg:sub(2))
+ if prefix == "." then truncate = value
+ elseif prefix == "+" then increment = value
+ elseif prefix == "@" then default = value
+ end
+ end
+ end
+
+ min = tonumber(min) assert(type(min) == "number", "debug_menu_add_slider: min must be a number")
+ max = tonumber(max) assert(type(max) == "number", "debug_menu_add_slider: max must be a number")
+ assert(type(truncate) == "nil" or type(truncate) == "number", "debug_menu_add_slider: truncate must be a number")
+ assert(type(truncate) == "nil" or type(increment) == "number", "debug_menu_add_slider: increment must be a number")
+
+ DebugMenu:AddSlider(currentUserCategory, DoUniqueString("slider"), text or "Slider", min or 0, max or 1, isPercentage or false, command or "echo Slider value changed", truncate, increment, default)
+end, "Adds a new slider to the current category")
+
+RegisterAlyxLibCommand("debug_menu_add_cycle", function (_, title, ...)
+ local rawValues = {...}
+ local parsedValues = {}
+
+ if title == nil then
+ return warn("debug_menu_add_cycle: No title specified")
+ end
+
+ -- parse each text:cmd pair
+ local i = 0
+ for _, pair in pairs(rawValues) do
+ i = i + 1
+ local text,value = pair:match("([^:]+):(.+)")
+ if text == nil or value == nil then
+ break
+ end
+ table.insert(parsedValues, {text = text, value = value})
+ end
+
+ if #parsedValues == 0 then
+ return warn("debug_menu_add_cycle: No values specified")
+ end
+
+ -- parse any leftover default
+ local default = tonumber(rawValues[i])
+ if default ~= nil and parsedValues[default] ~= nil then
+ default = parsedValues[default].value
+ end
+
+ DebugMenu:AddCycle(currentUserCategory, DoUniqueString("cycle"), title or "Cycle", parsedValues, function (index, item, cycle)
+ SendToConsole(item.value)
+ end, default)
+end, "Adds a new cycle to the current category")
+
+---@type string[]
+local cycleMapCmds = {}
+
+RegisterAlyxLibCommand("debug_menu_add_cycle_cmd", function(_, ...)
+ for _, cmd in ipairs({...}) do
+ table.insert(cycleMapCmds, cmd)
+ end
+end, "Defines a command for a cycle to be added with debug_menu_add_cycle_map")
+
+RegisterAlyxLibCommand("debug_menu_add_cycle_map", function(_, title, ...)
+ if #cycleMapCmds == 0 then
+ return warn("debug_menu_add_cycle_map: No commands defined with debug_menu_add_cycle_cmd")
+ end
+
+ local _cycleMapCmds = cycleMapCmds
+ cycleMapCmds = {}
+
+ ---@type string[]
+ local rawValues = {...}
+ local parsedValues = {}
+
+ if title == nil then
+ return warn("debug_menu_add_cycle: No title specified")
+ end
+
+ -- parse each text:cmd pair
+ local i = 0
+ for _, pair in pairs(rawValues) do
+ i = i + 1
+ local text,value = pair:match("([^:]+):(.+)")
+ if text == nil or value == nil then
+ break
+ end
+ ---@cast text string
+ ---@cast value string
+
+ -- single cmd cycles use convar
+ if #_cycleMapCmds == 1 then
+ table.insert(parsedValues, {text = text, value = value})
+ else
+ local cmdValues = value:split()
+ if #cmdValues ~= #_cycleMapCmds then
+ return warn("debug_menu_add_cycle_map: Number of values must match number of commands")
+ end
+
+ -- build full command string from values
+ local cmdStr = ""
+ for j = 1, #_cycleMapCmds do
+ cmdStr = cmdStr .. _cycleMapCmds[j] .. " " .. cmdValues[j]
+ if j < #_cycleMapCmds then
+ cmdStr = cmdStr .. "; "
+ end
+ end
+
+ table.insert(parsedValues, {text = text, value = cmdStr})
+ end
+ end
+
+ if #parsedValues == 0 then
+ return warn("debug_menu_add_cycle: No values specified")
+ end
+
+ -- parse any leftover default
+ local default = tonumber(rawValues[i])
+ if default ~= nil and parsedValues[default] ~= nil then
+ default = parsedValues[default].value
+ end
+
+ if #_cycleMapCmds == 1 then
+ DebugMenu:AddCycle(currentUserCategory, DoUniqueString("cycle"), title or "Cycle", parsedValues, _cycleMapCmds[1], default)
+ else
+ DebugMenu:AddCycle(currentUserCategory, DoUniqueString("cycle"), title or "Cycle", parsedValues, function (index, item, cycle)
+ SendToConsole(item.value)
+ end, default)
+ end
+
+end, "Adds a new cycle to the current category using previously defined commands")
+
+RegisterAlyxLibCommand("debug_menu_add_label", function (_, text)
+ DebugMenu:AddLabel(currentUserCategory, DoUniqueString("label"), text)
+end, "Adds a new label to the current category")
+
+RegisterAlyxLibCommand("debug_menu_add_separator", function (_, text)
+ DebugMenu:AddSeparator(currentUserCategory, nil, text)
+end, "Adds a new separator to the current category")
+
+---Execute the debug_menu.cfg file
+ListenToPlayerEvent("player_activate", function()
+ SendToConsole("exec debug_menu")
+end)
+
+return version
\ No newline at end of file
diff --git a/scripts/vscripts/alyxlib/init.lua b/scripts/vscripts/alyxlib/init.lua
index 50277fc..1144de7 100644
--- a/scripts/vscripts/alyxlib/init.lua
+++ b/scripts/vscripts/alyxlib/init.lua
@@ -95,6 +95,7 @@ else
alyxlib_require "alyxlib.debug.novr"
end
alyxlib_require "alyxlib.debug.debug_menu"
+alyxlib_require "alyxlib.debug.debug_menu_cfg"
-- Common third-party libraries
From 696e4d96e2f183790fb9406f51cb4f73a49c0718 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Sun, 14 Sep 2025 22:49:20 +1200
Subject: [PATCH 039/146] remove old tooling footer
---
.vscode/alyxlib_tooling.code-snippets | 1 -
1 file changed, 1 deletion(-)
diff --git a/.vscode/alyxlib_tooling.code-snippets b/.vscode/alyxlib_tooling.code-snippets
index f85e7eb..8a55ec5 100644
--- a/.vscode/alyxlib_tooling.code-snippets
+++ b/.vscode/alyxlib_tooling.code-snippets
@@ -47,7 +47,6 @@
"scope": "lua",
"prefix": ["snippet", "tool", "footer initialized"],
"body": [
- "print(\"${1:name}.lua \".. ${2:version} ..\" initialized...\")",
"",
"return ${2:version}",
],
From 3ae6ca8c2de164ea7faa71d7ac3a8cd43bed929f Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Tue, 16 Sep 2025 18:08:09 +1200
Subject: [PATCH 040/146] add GetBoundingOBBData
---
scripts/vscripts/alyxlib/math/obb.lua | 20 +++++++++++++++-----
1 file changed, 15 insertions(+), 5 deletions(-)
diff --git a/scripts/vscripts/alyxlib/math/obb.lua b/scripts/vscripts/alyxlib/math/obb.lua
index f3d19ae..3aa417d 100644
--- a/scripts/vscripts/alyxlib/math/obb.lua
+++ b/scripts/vscripts/alyxlib/math/obb.lua
@@ -36,13 +36,12 @@ end
---@field half Vector # The half extents of the OBB in local space.
---
----Returns the center and half extents of an entity's OBB in local space.
+---Returns the center and half extents of an OBB in local space.
---
----@param entity EntityHandle # The entity.
+---@param mins Vector # The local space minimum corner.
+---@param maxs Vector # The local space maximum corner.
---@return OBBData # The OBB data.
-function GetEntityOBBData(entity)
- local mins = entity:GetBoundingMins()
- local maxs = entity:GetBoundingMaxs()
+function GetBoundingOBBData(mins, maxs)
local center = (mins + maxs) * 0.5
local half = (maxs - mins) * 0.5
return {
@@ -51,6 +50,17 @@ function GetEntityOBBData(entity)
}
end
+---
+---Returns the center and half extents of an entity's OBB in local space.
+---
+---@param entity EntityHandle # The entity.
+---@return OBBData # The OBB data.
+function GetEntityOBBData(entity)
+ local mins = entity:GetBoundingMins()
+ local maxs = entity:GetBoundingMaxs()
+ return GetBoundingOBBData(mins, maxs)
+end
+
---
---Returns the world space minimum and maximum corners of an entity's OBB.
---
From 1d57b2816bacdd6660ef9ac3b1f82b4505c478c1 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Tue, 16 Sep 2025 18:11:15 +1200
Subject: [PATCH 041/146] add alyxlib_debug_menu_height cvar
---
.../scripts/custom_game/alyxlib_debug_menu.js | 15 +++++++++++++++
.../styles/custom_game/alyxlib_debug_menu.css | 8 ++++++++
scripts/vscripts/alyxlib/debug/commands.lua | 10 ++++++++--
scripts/vscripts/alyxlib/debug/debug_menu.lua | 13 ++++++++++++-
4 files changed, 43 insertions(+), 3 deletions(-)
diff --git a/panorama/scripts/custom_game/alyxlib_debug_menu.js b/panorama/scripts/custom_game/alyxlib_debug_menu.js
index 50d8705..393c6fe 100644
--- a/panorama/scripts/custom_game/alyxlib_debug_menu.js
+++ b/panorama/scripts/custom_game/alyxlib_debug_menu.js
@@ -988,6 +988,15 @@ function ClickHoveredButton()
}
}
+/**
+ * Sets the height of the menu container.
+ * @param {number} height
+ */
+function SetContainerHeight(height)
+{
+ $('#CategoriesContainer').style.minHeight = `${height}px`;
+}
+
/**
* Parses the incoming Lua command.
* @param {string} command
@@ -1153,6 +1162,12 @@ function ParseCommand(command, args)
categories.splice(index, 0, category);
}
}
+
+ case "setheight": {
+ const height = parseInt(args[0]);
+ SetContainerHeight(height);
+ break;
+ }
}
}
diff --git a/panorama/styles/custom_game/alyxlib_debug_menu.css b/panorama/styles/custom_game/alyxlib_debug_menu.css
index bcef003..2e25a38 100644
--- a/panorama/styles/custom_game/alyxlib_debug_menu.css
+++ b/panorama/styles/custom_game/alyxlib_debug_menu.css
@@ -6,7 +6,15 @@
background-color: gradient( radial, 50% 50%, 0% 0%, 100% 100%, from( #553c18 ), color-stop( 0.97, #000000 ), to( #000000 ) );
box-shadow: inset #995e0011 2px 2px 4px 4px;
opacity: .9;
+ height: 100%;
+}
+.vr_affordance_container{
+ height: 100%;
}
+#layout_root{
+ height: 100%;
+}
+
#background_panel .row .col_body
{
/*overflow: scroll;*/
diff --git a/scripts/vscripts/alyxlib/debug/commands.lua b/scripts/vscripts/alyxlib/debug/commands.lua
index 6f26488..395b17a 100644
--- a/scripts/vscripts/alyxlib/debug/commands.lua
+++ b/scripts/vscripts/alyxlib/debug/commands.lua
@@ -31,9 +31,15 @@ end
---@param defaultValue string # Default value of the convar.
---@param helpText? string # Description of the convar.
---@param flags? integer # Flags for the convar.
-function RegisterAlyxLibConvar(name, defaultValue, helpText, flags)
+---@param callback? fun(newVal:string, oldVal:string): any # Update function called after the value has been changed.
+function RegisterAlyxLibConvar(name, defaultValue, helpText, flags, callback)
helpText = helpText or "[No description]"
- Convars:RegisterConvar(name, defaultValue, helpText, flags or 0)
+ flags = flags or 0
+ if callback then
+ EasyConvars:RegisterConvar(name, defaultValue, helpText, flags, callback)
+ else
+ Convars:RegisterConvar(name, defaultValue, helpText, flags)
+ end
alyxlibCommands[name] = "Default: "..tostring(defaultValue)..", "..helpText
end
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index de99626..c9a12a5 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -11,6 +11,13 @@ end, "Forces the debug menu to show")
RegisterAlyxLibConvar("alyxlib_debug_menu_hand", "1", "Hand to attach the debug menu to, 0 = Secondary : 1 = Primary")
+RegisterAlyxLibConvar("alyxlib_debug_menu_height", "12", "Height of the debug menu, min=7 : max=30", 0, function(newVal, oldVal)
+ if DebugMenu:IsOpen() then
+ DebugMenu:CloseMenu()
+ DebugMenu:ShowMenu()
+ end
+end)
+
---
---The debug menu allows for easier VR testing by offering a customizable in-game menu.
---
@@ -188,7 +195,7 @@ function DebugMenu:ShowMenu()
targetname = "alyxlib_debug_menu",
dialog_layout_name = "file://{resources}/layout/custom_game/alyxlib_debug_menu.xml",
width = 16,--24,
- height = 12,--16
+ height = Convars:GetInt("alyxlib_debug_menu_height"),--12,--16
panel_dpi = 64,
ignore_input = 0,
lit = 0,
@@ -242,6 +249,10 @@ function DebugMenu:ShowMenu()
debugMenuOpen = true
end, 0.2)
+ local panelHeight = Clamp(Convars:GetInt("alyxlib_debug_menu_height"), 7, 30)
+ local cssHeight = (64 * panelHeight) - 111
+ Panorama:Send(self.panel, "SetHeight", cssHeight)
+
self:SendCategoriesToPanel()
end
From e4fe66aaca76b39ba0cc2dc7be65d023a94d009d Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Tue, 16 Sep 2025 18:11:27 +1200
Subject: [PATCH 042/146] ignore mkdocs cache
---
.gitignore | 1 +
1 file changed, 1 insertion(+)
diff --git a/.gitignore b/.gitignore
index 39a0086..61f8fcd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,4 @@ __pycache__
__test*
*.bak
check_lua_versions.sh
+.cache/
\ No newline at end of file
From 5fcd5dd6fbf3315dbb73b0c720c2d64fa188c191 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Tue, 16 Sep 2025 22:11:38 +1200
Subject: [PATCH 043/146] separate convar-callback in debug menu items
---
.../scripts/custom_game/alyxlib_debug_menu.js | 1 +
scripts/vscripts/alyxlib/debug/debug_menu.lua | 114 ++++++++----------
.../vscripts/alyxlib/debug/debug_menu_cfg.lua | 30 +++--
.../alyxlib/debug/debug_menu_extras.lua | 2 +-
4 files changed, 73 insertions(+), 74 deletions(-)
diff --git a/panorama/scripts/custom_game/alyxlib_debug_menu.js b/panorama/scripts/custom_game/alyxlib_debug_menu.js
index 393c6fe..c85d720 100644
--- a/panorama/scripts/custom_game/alyxlib_debug_menu.js
+++ b/panorama/scripts/custom_game/alyxlib_debug_menu.js
@@ -1161,6 +1161,7 @@ function ParseCommand(command, args)
categories.splice(currentPos, 1);
categories.splice(index, 0, category);
}
+ break;
}
case "setheight": {
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index c9a12a5..4fabca9 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -68,6 +68,10 @@ if not IsVREnabled() then
end, "", FCVAR_HIDDEN)
end
+local function empty(str)
+ return type(str) ~= "string" or str == ""
+end
+
---
---The scope of the debug menu script.
---
@@ -104,7 +108,7 @@ local debugPanelScriptScope = {
end
-- Update default if user is tracking manually
- if item.default ~= nil and type(item.default) ~= "function" then
+ if empty(item.convar) or (item.default ~= nil and type(item.default) ~= "function") then
item.default = on
end
@@ -408,23 +412,20 @@ end
---@param categoryId string # The category ID to add the toggle to
---@param toggleId string # The unique ID for this toggle
---@param text string # The text to display on this toggle
----@param command string|function # The console command or function to run when this toggle is toggled (will run with 1 if it's on, 0 if it's off)
+---@param convar? string # The console variable tied to this toggle
+---@param callback? fun(on:boolean) # Function to run when this toggle is toggled
---@param startsOn? boolean|fun():boolean # Whether the toggle is on by default
-function DebugMenu:AddToggle(categoryId, toggleId, text, command, startsOn)
+function DebugMenu:AddToggle(categoryId, toggleId, text, convar, callback, startsOn)
local category = self:GetCategory(categoryId)
if not category then
warn("Cannot add toggle '"..toggleId.."': Category '"..categoryId.."' does not exist!")
return
end
- local callback
- if type(command) == "string" then
- startsOn = startsOn or Convars:GetBool(command) or false
+ if callback == nil and not empty(convar) then
callback = function(on)
- SendToConsole(command .. " " .. (on and 1 or 0))
+ SendToConsole(convar .. " " .. (on and 1 or 0))
end
- elseif type(command) == "function" then
- callback = command
end
table.insert(category.items, {
@@ -433,7 +434,8 @@ function DebugMenu:AddToggle(categoryId, toggleId, text, command, startsOn)
text = text,
callback = callback,
type = "toggle",
- default = startsOn or false,
+ default = startsOn,
+ convar = convar
})
end
@@ -464,35 +466,27 @@ end
---@param categoryId string # The ID of the category to add this slider to
---@param sliderId string # A unique ID for this slider
---@param text string # Display text for the slider
+---@param convar string # The console variable to tie this slider to
+---@param callback? fun(value:number,slider:DebugMenuItem) # Callback function
---@param min number # Minimum allowed value
---@param max number # Maximum allowed value
---@param isPercentage boolean # If true, value will be displayed as a percentage (0-100)
----@param command string|fun(value:number,slider:DebugMenuItem) # Convar name or callback function
---@param truncate? number # Number of decimal places (0 = integer, -1 = no truncating)
---@param increment? number # Snap increment (0 disables snapping)
---@param defaultValue? number|fun():number # Starting value. Set nil to use the convar value whenever the menu opens
-function DebugMenu:AddSlider(categoryId, sliderId, text, min, max, isPercentage, command, truncate, increment, defaultValue)
+function DebugMenu:AddSlider(categoryId, sliderId, text, convar, callback, min, max, isPercentage, truncate, increment, defaultValue)
local category = self:GetCategory(categoryId)
if not category then
warn("Cannot add toggle '"..sliderId.."': Category '"..categoryId.."' does not exist!")
return
end
- local callback
- local convar = ""
- if type(command) == "string" then
- if command == "" then
- error("Command must not be a blank string", 2)
- end
- convar = command
-
+ if callback == nil and type(convar) == "string" and convar ~= "" then
---@param value number
---@param slider DebugMenuItem
callback = function(value, slider)
Convars:SetStr(slider.convar, tostring(value))
end
- elseif type(command) == "function" then
- callback = command
end
table.insert(category.items, {
@@ -519,10 +513,11 @@ end
---@param categoryId string # The id of the category to add this cycle to
---@param cycleId string # The unique id for this new cycle
---@param title string|nil # The text to display next to each value
+---@param convar? string # The console variable tied to this cycle
+---@param callback? fun(index:number, item:{text:string,value:any?}, cycle:DebugMenuItem) # Function callback
---@param values {text:string,value:any}[]|string[] # List of text/value pairs for this cycle, or a list of values
----@param command string|fun(index:number, item:{text:string,value:any?}, cycle:DebugMenuItem) # Convar name or function callback
---@param defaultValue? any|fun():any # Value for this cycle to start with
-function DebugMenu:AddCycle(categoryId, cycleId, title, values, command, defaultValue)
+function DebugMenu:AddCycle(categoryId, cycleId, title, convar, callback, values, defaultValue)
local category = self:GetCategory(categoryId)
if not category then
warn("Cannot add toggle '"..cycleId.."': Category '"..categoryId.."' does not exist!")
@@ -546,37 +541,24 @@ function DebugMenu:AddCycle(categoryId, cycleId, title, values, command, default
end
end
- local callback
- local convar = ""
- if type(command) == "string" then
- if command == "" then
- error("Command must not be a blank string", 2)
- end
- convar = command
-
+ if callback == nil and not empty(convar) then
---@param index number
---@param item {text:string,value:any?}
---@param cycle DebugMenuItem
callback = function(index, item, cycle)
Convars:SetStr(cycle.convar, tostring(item.value))
end
- elseif type(command) == "function" then
- callback = command
-
- if defaultValue == nil then
- defaultValue = parsedValues[1].value
- end
end
table.insert(category.items, {
categoryId = categoryId,
id = cycleId,
- callback = callback,
type = "cycle",
+ text = title,
+ convar = convar,
+ callback = callback,
values = parsedValues,
default = defaultValue,
- convar = convar,
- text = title
})
end
@@ -631,12 +613,24 @@ function DebugMenu:SetCategoryIndex(categoryId, index)
end
---Resolves the default value of an element by running any value getter functions.
----@param default any|fun():any # The default value to resolve
+---@param item DebugMenuItem # The item to resolve
+---@param tFunc? `Convars.GetStr`|`Convars.GetInt`|`Convars.GetFloat`|`Convars.GetBool` # The value getter function
+---@param default? any # The default value
---@return any # The resolved value
-local function resolveDefault(default)
- if type(default) == "function" then
- return default()
+local function resolveDefault(item, tFunc, default)
+ if type(item.default) == "function" then
+ return item.default()
+ end
+
+ if item.default ~= nil then
+ return item.default
end
+
+ if item.convar and item.convar ~= "" then
+ tFunc = tFunc or Convars.GetStr
+ return tFunc(Convars, item.convar)
+ end
+
return default
end
@@ -656,12 +650,12 @@ function DebugMenu:SendCategoryToPanel(category)
Panorama:Send(panel, "AddCategory", category.id, category.name)
for _, item in ipairs(category.items) do
- if item.type == "toggle" then
- Panorama:Send(panel, "AddToggle", item.categoryId, item.id, item.text, resolveDefault(item.default))
-
- elseif item.type == "button" then
+ if item.type == "button" then
Panorama:Send(panel, "AddButton", item.categoryId, item.id, item.text)
+ elseif item.type == "toggle" then
+ Panorama:Send(panel, "AddToggle", item.categoryId, item.id, item.text, resolveDefault(item, Convars.GetBool, false))
+
elseif item.type == "label" then
Panorama:Send(panel, "AddLabel", item.categoryId, item.id, item.text)
@@ -669,24 +663,16 @@ function DebugMenu:SendCategoryToPanel(category)
Panorama:Send(panel, "AddSeparator", item.categoryId, item.id, item.text)
elseif item.type == "slider" then
- local default = resolveDefault(item.default)
- if default == nil then
- default = Convars:GetFloat(item.convar) or item.min
- end
+ local default = resolveDefault(item, Convars.GetFloat, item.min)
Panorama:Send(panel, "AddSlider", item.categoryId, item.id, item.text or item.convar, item.convar, item.min, item.max, default, item.isPercentage, item.truncate, item.increment)
elseif item.type == "cycle" then
- local default = resolveDefault(item.default)
-
- -- Use convar value if default isn't set
- if default == nil and item.convar ~= "" then
- default = Convars:GetStr(item.convar)
- end
+ local default = resolveDefault(item)
if default ~= nil then
-- find the index of the default value
- local index = TableFindIndex(item.values, function(v) return v.value == default end)
+ local index = TableFindIndex(item.values, function(v) return tostring(v.value) == tostring(default) end)
if index > 0 then
default = index
else
@@ -810,19 +796,19 @@ DebugMenu:AddCategory(categoryId, "AlyxLib")
DebugMenu:AddSeparator(categoryId, nil, "Basic")
if IsVREnabled() then
- DebugMenu:AddToggle(categoryId, "noclip_vr", "NoClip VR", "noclip_vr", function ()
+ DebugMenu:AddToggle(categoryId, "noclip_vr", "NoClip VR", "noclip_vr", nil, function()
return Convars:GetBool("noclip_vr_enabled")
end)
DebugMenu:AddLabel(categoryId, "noclip_vr_label", "Hold movement trigger to boost")
- DebugMenu:AddSlider(categoryId, "noclip_vr_speed", "NoClip VR Speed", 0.5, 10, false, "noclip_vr_speed", 2)
- DebugMenu:AddSlider(categoryId, "noclip_vr_boost_speed", "NoClip VR Boost Speed", 0.5, 10, false, "noclip_vr_boost_speed", 2)
+ DebugMenu:AddSlider(categoryId, "noclip_vr_speed", "NoClip VR Speed", "noclip_vr_speed", nil, 0.5, 10, false, 2)
+ DebugMenu:AddSlider(categoryId, "noclip_vr_boost_speed", "NoClip VR Boost Speed", "noclip_vr_boost_speed", nil, 0.5, 10, false, 2)
end
DebugMenu:AddToggle(categoryId, "buddha", "Buddha Mode", "buddha")
DebugMenu:AddToggle(categoryId, "lefthanded", "Left Handed", "hlvr_left_hand_primary")
-DebugMenu:AddToggle(categoryId, "gameinstructor", "Game Instructor Hints",
+DebugMenu:AddToggle(categoryId, "gameinstructor", "Game Instructor Hints", nil,
function(on)
Convars:SetBool("gameinstructor_enable", on)
Convars:SetBool("sv_gameinstructor_disable", not on)
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu_cfg.lua b/scripts/vscripts/alyxlib/debug/debug_menu_cfg.lua
index bf24d5f..023d9c9 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu_cfg.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu_cfg.lua
@@ -30,10 +30,22 @@ RegisterAlyxLibCommand("debug_menu_add_toggle", function (_, text, commandOff, c
default = (default == "on" or default == "1")
- local command = commandOff
+ local callback
+ local convar
+
+ if string.find(commandOff, "%s") then
+ -- commandOff is a convar with a value
+ callback = function(on)
+ SendToConsole(commandOff)
+ end
+ convar = string.sub(commandOff, 1, string.find(commandOff, "%s")-1)
+ else
+ -- commandOff is just a convar
+ convar = commandOff
+ end
if commandOn ~= nil then
- command = function(isOn)
+ callback = function(isOn)
if isOn then
SendToConsole(commandOn)
else
@@ -42,7 +54,7 @@ RegisterAlyxLibCommand("debug_menu_add_toggle", function (_, text, commandOff, c
end
end
- DebugMenu:AddToggle(currentUserCategory, DoUniqueString("toggle"), text or "Toggle", command or "echo Toggle pressed", default)
+ DebugMenu:AddToggle(currentUserCategory, DoUniqueString("toggle"), text or "Toggle", convar, callback, default)
end, "Adds a new toggle to the current category")
RegisterAlyxLibCommand("debug_menu_add_slider", function (_, text, command, min, max, ...)
@@ -70,7 +82,7 @@ RegisterAlyxLibCommand("debug_menu_add_slider", function (_, text, command, min,
assert(type(truncate) == "nil" or type(truncate) == "number", "debug_menu_add_slider: truncate must be a number")
assert(type(truncate) == "nil" or type(increment) == "number", "debug_menu_add_slider: increment must be a number")
- DebugMenu:AddSlider(currentUserCategory, DoUniqueString("slider"), text or "Slider", min or 0, max or 1, isPercentage or false, command or "echo Slider value changed", truncate, increment, default)
+ DebugMenu:AddSlider(currentUserCategory, DoUniqueString("slider"), text or "Slider", command, nil, min or 0, max or 1, isPercentage or false, truncate, increment, default)
end, "Adds a new slider to the current category")
RegisterAlyxLibCommand("debug_menu_add_cycle", function (_, title, ...)
@@ -102,9 +114,9 @@ RegisterAlyxLibCommand("debug_menu_add_cycle", function (_, title, ...)
default = parsedValues[default].value
end
- DebugMenu:AddCycle(currentUserCategory, DoUniqueString("cycle"), title or "Cycle", parsedValues, function (index, item, cycle)
+ DebugMenu:AddCycle(currentUserCategory, DoUniqueString("cycle"), title or "Cycle", nil, function (index, item, cycle)
SendToConsole(item.value)
- end, default)
+ end, parsedValues, default)
end, "Adds a new cycle to the current category")
---@type string[]
@@ -176,11 +188,11 @@ RegisterAlyxLibCommand("debug_menu_add_cycle_map", function(_, title, ...)
end
if #_cycleMapCmds == 1 then
- DebugMenu:AddCycle(currentUserCategory, DoUniqueString("cycle"), title or "Cycle", parsedValues, _cycleMapCmds[1], default)
+ DebugMenu:AddCycle(currentUserCategory, DoUniqueString("cycle"), title or "Cycle", _cycleMapCmds[1], nil, parsedValues, default)
else
- DebugMenu:AddCycle(currentUserCategory, DoUniqueString("cycle"), title or "Cycle", parsedValues, function (index, item, cycle)
+ DebugMenu:AddCycle(currentUserCategory, DoUniqueString("cycle"), title or "Cycle", nil, function (index, item, cycle)
SendToConsole(item.value)
- end, default)
+ end, parsedValues, default)
end
end, "Adds a new cycle to the current category using previously defined commands")
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu_extras.lua b/scripts/vscripts/alyxlib/debug/debug_menu_extras.lua
index e4b9dac..e1449d0 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu_extras.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu_extras.lua
@@ -23,7 +23,7 @@ DebugMenu:AddToggle(categoryId, "fullbright", "Fullbright", "mat_fullbright")
DebugMenu:AddToggle(categoryId, "visibility", "Vis", "vis_enable")
-DebugMenu:AddToggle(categoryId, categoryId.."_visfreeze", "Freeze Vis", function(on)
+DebugMenu:AddToggle(categoryId, categoryId.."_visfreeze", "Freeze Vis", nil, function(on)
if on then
SendToConsole("vis_debug_show 1")
Player:Delay(function()
From 4fc582efd84bb5d52c17179f1d1627b78e7e6219 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Tue, 16 Sep 2025 22:14:13 +1200
Subject: [PATCH 044/146] pass menu item into every callback
---
scripts/vscripts/alyxlib/debug/debug_menu.lua | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index 4fabca9..420dece 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -91,7 +91,7 @@ local debugPanelScriptScope = {
end
if item.callback then
- item.callback()
+ item.callback(item)
end
end,
@@ -113,7 +113,7 @@ local debugPanelScriptScope = {
end
if item.callback then
- item.callback(on)
+ item.callback(on, item)
end
end,
@@ -380,7 +380,7 @@ end
---@param categoryId string # The category ID to add the button to
---@param buttonId string # The unique ID for this button
---@param text string # The text to display on this button
----@param command string|function # The console command or function to run when this button is pressed
+---@param command string|fun(button:DebugMenuItem) # The console command or function to run when this button is pressed
function DebugMenu:AddButton(categoryId, buttonId, text, command)
local category = self:GetCategory(categoryId)
if not category then
@@ -413,7 +413,7 @@ end
---@param toggleId string # The unique ID for this toggle
---@param text string # The text to display on this toggle
---@param convar? string # The console variable tied to this toggle
----@param callback? fun(on:boolean) # Function to run when this toggle is toggled
+---@param callback? fun(on:boolean,toggle:DebugMenuItem) # Function to run when this toggle is toggled
---@param startsOn? boolean|fun():boolean # Whether the toggle is on by default
function DebugMenu:AddToggle(categoryId, toggleId, text, convar, callback, startsOn)
local category = self:GetCategory(categoryId)
From 5fe9c5102d207a877456e142c91cb5391cb23448 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Tue, 16 Sep 2025 22:18:25 +1200
Subject: [PATCH 045/146] fail without debug_menu_add_category
---
.../vscripts/alyxlib/debug/debug_menu_cfg.lua | 26 +++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu_cfg.lua b/scripts/vscripts/alyxlib/debug/debug_menu_cfg.lua
index 023d9c9..0fa5429 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu_cfg.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu_cfg.lua
@@ -15,10 +15,17 @@ RegisterAlyxLibCommand("debug_menu_add_category", function (_, name)
end, "Adds a new category to the debug menu")
RegisterAlyxLibCommand("debug_menu_add_button", function (_, text, command)
+ if currentUserCategory == nil or currentUserCategory == "" then
+ return warn("debug_menu_add_button: No category defined with debug_menu_add_category")
+ end
DebugMenu:AddButton(currentUserCategory, DoUniqueString("button"), text or "Button", command or "echo Button pressed")
end, "Adds a new button to the current category")
RegisterAlyxLibCommand("debug_menu_add_toggle", function (_, text, commandOff, commandOn, default)
+ if currentUserCategory == nil or currentUserCategory == "" then
+ return warn("debug_menu_add_toggle: No category defined with debug_menu_add_category")
+ end
+
if default == nil then
if commandOn ~= nil and vlua.find({"off","on","0","1"}, string.lower(commandOn)) then
default = string.lower(commandOn)
@@ -58,6 +65,9 @@ RegisterAlyxLibCommand("debug_menu_add_toggle", function (_, text, commandOff, c
end, "Adds a new toggle to the current category")
RegisterAlyxLibCommand("debug_menu_add_slider", function (_, text, command, min, max, ...)
+ if currentUserCategory == nil or currentUserCategory == "" then
+ return warn("debug_menu_add_slider: No category defined with debug_menu_add_category")
+ end
local isPercentage = false
local truncate = nil
@@ -86,6 +96,10 @@ RegisterAlyxLibCommand("debug_menu_add_slider", function (_, text, command, min,
end, "Adds a new slider to the current category")
RegisterAlyxLibCommand("debug_menu_add_cycle", function (_, title, ...)
+ if currentUserCategory == nil or currentUserCategory == "" then
+ return warn("debug_menu_add_cycle: No category defined with debug_menu_add_category")
+ end
+
local rawValues = {...}
local parsedValues = {}
@@ -129,6 +143,10 @@ RegisterAlyxLibCommand("debug_menu_add_cycle_cmd", function(_, ...)
end, "Defines a command for a cycle to be added with debug_menu_add_cycle_map")
RegisterAlyxLibCommand("debug_menu_add_cycle_map", function(_, title, ...)
+ if currentUserCategory == nil or currentUserCategory == "" then
+ return warn("debug_menu_add_cycle_map: No category defined with debug_menu_add_category")
+ end
+
if #cycleMapCmds == 0 then
return warn("debug_menu_add_cycle_map: No commands defined with debug_menu_add_cycle_cmd")
end
@@ -198,10 +216,18 @@ RegisterAlyxLibCommand("debug_menu_add_cycle_map", function(_, title, ...)
end, "Adds a new cycle to the current category using previously defined commands")
RegisterAlyxLibCommand("debug_menu_add_label", function (_, text)
+ if currentUserCategory == nil or currentUserCategory == "" then
+ return warn("debug_menu_add_label: No category defined with debug_menu_add_category")
+ end
+
DebugMenu:AddLabel(currentUserCategory, DoUniqueString("label"), text)
end, "Adds a new label to the current category")
RegisterAlyxLibCommand("debug_menu_add_separator", function (_, text)
+ if currentUserCategory == nil or currentUserCategory == "" then
+ return warn("debug_menu_add_separator: No category defined with debug_menu_add_category")
+ end
+
DebugMenu:AddSeparator(currentUserCategory, nil, text)
end, "Adds a new separator to the current category")
From f52a5dbfe7b2a5e2593c2f800a320c6477ac1add Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Tue, 16 Sep 2025 22:21:07 +1200
Subject: [PATCH 046/146] fix typo
---
scripts/vscripts/alyxlib/extensions/entities.lua | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/scripts/vscripts/alyxlib/extensions/entities.lua b/scripts/vscripts/alyxlib/extensions/entities.lua
index ce9f738..3244da2 100644
--- a/scripts/vscripts/alyxlib/extensions/entities.lua
+++ b/scripts/vscripts/alyxlib/extensions/entities.lua
@@ -15,7 +15,7 @@ local version = "v1.8.0"
---
---Finds the entity in a given list that is closest to a specified position.
----Uses 0`CalcClosestPointOnEntityOBB` to determine the nearest point on each entity's bounding box.
+---Uses `CalcClosestPointOnEntityOBB` to determine the nearest point on each entity's bounding box.
---
--- @param ents EntityHandle[] # A list of entities to check
--- @param pos Vector # The position to compare distances against
From a0e3f412fbfc99f5244ead87989c7e492b50cf4c Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Wed, 17 Sep 2025 23:39:11 +1200
Subject: [PATCH 047/146] move all callbacks to end of param list
this brings them all in-line with each other and also makes writing
convar items without callbacks cleaner
---
scripts/vscripts/alyxlib/debug/debug_menu.lua | 12 ++++++------
scripts/vscripts/alyxlib/debug/debug_menu_cfg.lua | 12 ++++++------
2 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index 420dece..ff56bb1 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -467,14 +467,14 @@ end
---@param sliderId string # A unique ID for this slider
---@param text string # Display text for the slider
---@param convar string # The console variable to tie this slider to
----@param callback? fun(value:number,slider:DebugMenuItem) # Callback function
---@param min number # Minimum allowed value
---@param max number # Maximum allowed value
---@param isPercentage boolean # If true, value will be displayed as a percentage (0-100)
---@param truncate? number # Number of decimal places (0 = integer, -1 = no truncating)
---@param increment? number # Snap increment (0 disables snapping)
+---@param callback? fun(value:number,slider:DebugMenuItem) # Callback function
---@param defaultValue? number|fun():number # Starting value. Set nil to use the convar value whenever the menu opens
-function DebugMenu:AddSlider(categoryId, sliderId, text, convar, callback, min, max, isPercentage, truncate, increment, defaultValue)
+function DebugMenu:AddSlider(categoryId, sliderId, text, convar, min, max, isPercentage, truncate, increment, callback, defaultValue)
local category = self:GetCategory(categoryId)
if not category then
warn("Cannot add toggle '"..sliderId.."': Category '"..categoryId.."' does not exist!")
@@ -514,10 +514,10 @@ end
---@param cycleId string # The unique id for this new cycle
---@param title string|nil # The text to display next to each value
---@param convar? string # The console variable tied to this cycle
----@param callback? fun(index:number, item:{text:string,value:any?}, cycle:DebugMenuItem) # Function callback
---@param values {text:string,value:any}[]|string[] # List of text/value pairs for this cycle, or a list of values
+---@param callback? fun(index:number, item:{text:string,value:any?}, cycle:DebugMenuItem) # Function callback
---@param defaultValue? any|fun():any # Value for this cycle to start with
-function DebugMenu:AddCycle(categoryId, cycleId, title, convar, callback, values, defaultValue)
+function DebugMenu:AddCycle(categoryId, cycleId, title, convar, values, callback, defaultValue)
local category = self:GetCategory(categoryId)
if not category then
warn("Cannot add toggle '"..cycleId.."': Category '"..categoryId.."' does not exist!")
@@ -800,8 +800,8 @@ if IsVREnabled() then
return Convars:GetBool("noclip_vr_enabled")
end)
DebugMenu:AddLabel(categoryId, "noclip_vr_label", "Hold movement trigger to boost")
- DebugMenu:AddSlider(categoryId, "noclip_vr_speed", "NoClip VR Speed", "noclip_vr_speed", nil, 0.5, 10, false, 2)
- DebugMenu:AddSlider(categoryId, "noclip_vr_boost_speed", "NoClip VR Boost Speed", "noclip_vr_boost_speed", nil, 0.5, 10, false, 2)
+ DebugMenu:AddSlider(categoryId, "noclip_vr_speed", "NoClip VR Speed", "noclip_vr_speed", 0.5, 10, false, 2)
+ DebugMenu:AddSlider(categoryId, "noclip_vr_boost_speed", "NoClip VR Boost Speed", "noclip_vr_boost_speed", 0.5, 10, false, 2)
end
DebugMenu:AddToggle(categoryId, "buddha", "Buddha Mode", "buddha")
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu_cfg.lua b/scripts/vscripts/alyxlib/debug/debug_menu_cfg.lua
index 0fa5429..288467c 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu_cfg.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu_cfg.lua
@@ -92,7 +92,7 @@ RegisterAlyxLibCommand("debug_menu_add_slider", function (_, text, command, min,
assert(type(truncate) == "nil" or type(truncate) == "number", "debug_menu_add_slider: truncate must be a number")
assert(type(truncate) == "nil" or type(increment) == "number", "debug_menu_add_slider: increment must be a number")
- DebugMenu:AddSlider(currentUserCategory, DoUniqueString("slider"), text or "Slider", command, nil, min or 0, max or 1, isPercentage or false, truncate, increment, default)
+ DebugMenu:AddSlider(currentUserCategory, DoUniqueString("slider"), text or "Slider", command, min or 0, max or 1, isPercentage or false, truncate, increment, nil, default)
end, "Adds a new slider to the current category")
RegisterAlyxLibCommand("debug_menu_add_cycle", function (_, title, ...)
@@ -128,9 +128,9 @@ RegisterAlyxLibCommand("debug_menu_add_cycle", function (_, title, ...)
default = parsedValues[default].value
end
- DebugMenu:AddCycle(currentUserCategory, DoUniqueString("cycle"), title or "Cycle", nil, function (index, item, cycle)
+ DebugMenu:AddCycle(currentUserCategory, DoUniqueString("cycle"), title or "Cycle", nil, parsedValues, function (index, item, cycle)
SendToConsole(item.value)
- end, parsedValues, default)
+ end, default)
end, "Adds a new cycle to the current category")
---@type string[]
@@ -206,11 +206,11 @@ RegisterAlyxLibCommand("debug_menu_add_cycle_map", function(_, title, ...)
end
if #_cycleMapCmds == 1 then
- DebugMenu:AddCycle(currentUserCategory, DoUniqueString("cycle"), title or "Cycle", _cycleMapCmds[1], nil, parsedValues, default)
+ DebugMenu:AddCycle(currentUserCategory, DoUniqueString("cycle"), title or "Cycle", _cycleMapCmds[1], parsedValues, nil, default)
else
- DebugMenu:AddCycle(currentUserCategory, DoUniqueString("cycle"), title or "Cycle", nil, function (index, item, cycle)
+ DebugMenu:AddCycle(currentUserCategory, DoUniqueString("cycle"), title or "Cycle", nil, parsedValues, function (index, item, cycle)
SendToConsole(item.value)
- end, parsedValues, default)
+ end, default)
end
end, "Adds a new cycle to the current category using previously defined commands")
From ca42136063d00854a749e7b5209adf771e3ae967 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Wed, 17 Sep 2025 23:39:36 +1200
Subject: [PATCH 048/146] error on invalid player event
---
scripts/vscripts/alyxlib/player/events.lua | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/scripts/vscripts/alyxlib/player/events.lua b/scripts/vscripts/alyxlib/player/events.lua
index 3458693..b554853 100644
--- a/scripts/vscripts/alyxlib/player/events.lua
+++ b/scripts/vscripts/alyxlib/player/events.lua
@@ -40,10 +40,14 @@ local playerActivated = false
---@param context? table # Optional: The context to pass to the function as `self`. If omitted the context will not passed to the callback.
---@return integer eventID # ID used to unregister
function ListenToPlayerEvent(event, callback, context)
+ assert(registered_event_callbacks[event], "Unknown player event "..event)
+
if playerActivated and (event == "novr_player" or event == "player_activate" or event == "vr_player_ready") then
warn("Player has already spawned so this event won't fire at "..Debug.GetSourceLine(3))
end
+
devprint2("Listening to player event", event, callback)
+
registered_event_callbacks[event][registered_event_index] = { callback = callback, context = context}
registered_event_index = registered_event_index + 1
return registered_event_index - 1
From 579c1f49b2b1e1698ec7d1d00f0d8a26d67ccd15 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Wed, 17 Sep 2025 23:43:55 +1200
Subject: [PATCH 049/146] add runtime precaching
GlobalPrecacheFlush now triggers another precache spawn at any time
---
scripts/vscripts/alyxlib/precache.lua | 49 +++++++++++++++++++++++----
1 file changed, 42 insertions(+), 7 deletions(-)
diff --git a/scripts/vscripts/alyxlib/precache.lua b/scripts/vscripts/alyxlib/precache.lua
index d728826..f9d8839 100644
--- a/scripts/vscripts/alyxlib/precache.lua
+++ b/scripts/vscripts/alyxlib/precache.lua
@@ -52,11 +52,16 @@ require "alyxlib.player.core"
---@type AlyxLibGlobalPrecacheItem[]
_G.AlyxLibGlobalPrecacheList = {}
+---
---Add an asset to be precached when the player spawns.
+---
+---If you are precaching *after* the player has spawned, then you must also call [GlobalPrecacheFlush](lua://GlobalPrecacheFlush).
+---
---@param type AlyxLibGlobalPrecacheType # The type of asset to precache.
---@param path string # The asset path to precache (or the classname if type is entity).
---@param spawnkeys table? # The spawnkeys table if type is entity.
function GlobalPrecache(type, path, spawnkeys)
+ print("Adding global precache:", type, path)
table.insert(AlyxLibGlobalPrecacheList, {
type = type,
path = path,
@@ -64,6 +69,37 @@ function GlobalPrecache(type, path, spawnkeys)
})
end
+---Starts the precache process.
+---@param callback? function # The function to call when the precaching is complete.
+local function precacheAsync(callback)
+ SpawnEntityFromTableAsynchronous("logic_script", {
+ vscripts = "alyxlib/precache",
+ }, function (spawnedEnt)
+ if type(callback) == "function" then
+ callback()
+ end
+ spawnedEnt:Kill()
+ end, nil)
+end
+
+---
+---Flushes the global precache list and precaches the assets.
+---
+---This is an asynchronous process; the assets will not be immediately available after calling this function.
+---
+---If you are precaching *after* the player has spawned, call this function after preceding [GlobalPrecache](lua://GlobalPrecache) calls.
+---
+---@param callback function # The function to call when the precaching is complete.
+function GlobalPrecacheFlush(callback)
+ precacheAsync(function()
+ AlyxLibGlobalPrecacheList = {}
+ if type(callback) == "function" then
+ callback()
+ end
+ end)
+end
+
+---
---Internal function used to start the precache process.
---
---**This should only be called manually if you know what you're doing!**
@@ -72,7 +108,7 @@ function _PrecacheGlobalItems(context)
if #AlyxLibGlobalPrecacheList > 0 then
devprints("Globally precaching", #AlyxLibGlobalPrecacheList, "resources...")
for _, item in ipairs(AlyxLibGlobalPrecacheList) do
- devprints("\tPrecaching", item.type, item.path)
+ devprints2("\tPrecaching", item.type, item.path)
if item.type == "model" then
PrecacheModel(item.path, context)
elseif item.type == "entity" then
@@ -84,11 +120,10 @@ function _PrecacheGlobalItems(context)
end
end
-ListenToPlayerEvent("player_activate", function (params)
- SpawnEntityFromTableAsynchronous("logic_script", {
- vscripts = "alyxlib/precache"
- }, function (spawnedEnt)
- end, nil)
-end)
+---player_spawn seems to be the soonest time to async spawn without crashing.
+---@param params GameEventPlayerSpawn
+ListenToGameEvent("player_spawn", function (params)
+ precacheAsync()
+end, nil)
return version
\ No newline at end of file
From 7b0808412a9d4c3c70bde9dbd9963b117974e0f8 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Fri, 19 Sep 2025 23:49:07 +1200
Subject: [PATCH 050/146] add debug_menu_floating
also removed alyxlib_ prefix from commands
---
scripts/vscripts/alyxlib/debug/debug_menu.lua | 51 ++++++++++++++-----
1 file changed, 37 insertions(+), 14 deletions(-)
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index ff56bb1..ecb48bd 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -5,19 +5,28 @@
The debug menu allows for easier VR testing by offering a customizable in-game menu.
]]
-RegisterAlyxLibCommand("alyxlib_debug_menu_show", function (name, ...)
+RegisterAlyxLibCommand("debug_menu_show", function (name, ...)
DebugMenu:ShowMenu()
end, "Forces the debug menu to show")
-RegisterAlyxLibConvar("alyxlib_debug_menu_hand", "1", "Hand to attach the debug menu to, 0 = Secondary : 1 = Primary")
+RegisterAlyxLibConvar("debug_menu_hand", "0", "Hand to attach the debug menu to, 0 = Secondary : 1 = Primary")
-RegisterAlyxLibConvar("alyxlib_debug_menu_height", "12", "Height of the debug menu, min=7 : max=30", 0, function(newVal, oldVal)
+RegisterAlyxLibConvar("debug_menu_height", "14", "Height of the debug menu, min=7 : max=30", 0, function(newVal, oldVal)
if DebugMenu:IsOpen() then
DebugMenu:CloseMenu()
DebugMenu:ShowMenu()
end
end)
+RegisterAlyxLibConvar("debug_menu_floating", function()
+ -- Float menu for inside-out tracking
+ if Player:GetVRControllerType() == 3 then
+ return true
+ end
+
+ return false
+end, "Menu will float in world instead of attached to hand")
+
---
---The debug menu allows for easier VR testing by offering a customizable in-game menu.
---
@@ -176,7 +185,7 @@ local debugPanelScriptScope = {
---Updates the physical menu by attaching it to the correct hand.
---
function DebugMenu:UpdateMenuAttachment()
- local hand = Convars:GetBool("alyxlib_debug_menu_hand") and Player.PrimaryHand or Player.SecondaryHand
+ local hand = Convars:GetBool("debug_menu_hand") and Player.PrimaryHand or Player.SecondaryHand
if hand == Player.RightHand then
self.panel:SetParent(hand, "constraint1")
self.panel:ResetLocal()
@@ -198,8 +207,8 @@ function DebugMenu:ShowMenu()
self.panel = SpawnEntityFromTableSynchronous("point_clientui_world_panel", {
targetname = "alyxlib_debug_menu",
dialog_layout_name = "file://{resources}/layout/custom_game/alyxlib_debug_menu.xml",
- width = 16,--24,
- height = Convars:GetInt("alyxlib_debug_menu_height"),--12,--16
+ width = 16,
+ height = Convars:GetInt("debug_menu_height"),
panel_dpi = 64,
ignore_input = 0,
lit = 0,
@@ -221,23 +230,37 @@ function DebugMenu:ShowMenu()
SendToConsole("bind r _debug_menu_test_button_press")
else
- self:UpdateMenuAttachment()
+ local handType = -1
+ if Convars:GetBool("debug_menu_floating") then
+ local localPlayer = Entities:GetLocalPlayer()
+ local eyePos = localPlayer:EyePosition()
+ local dir = localPlayer:EyeAngles():Forward()
+ local a = VectorToAngles(dir)
+ a = RotateOrientation(a, QAngle(0,-90,90))
+ self.panel:SetQAngle(a)
+ self.panel:SetOrigin(eyePos + dir * 16)
+ self.panel:SetParent(GetWorld(), nil)
+ else
+ self:UpdateMenuAttachment()
+
+ handType = Convars:GetInt("debug_menu_hand") == 1 and InputHandSecondary or InputHandPrimary
+
+ handChangedListener = ListenToPlayerEvent("primary_hand_changed", function()
+ self:UpdateMenuAttachment()
+ end)
+ end
-- Cough handpose gets in the way for close menus
Player:SetCoughHandEnabled(false)
-- Handle distant button presses
Input:ListenToButton("press",
- Convars:GetInt("alyxlib_debug_menu_hand") == 1 and InputHandSecondary or InputHandPrimary,
+ handType,
DIGITAL_INPUT_MENU_INTERACT, 1,
- function (params)
+ function (context, params)
self:ClickHoveredButton()
end, self)
- handChangedListener = ListenToPlayerEvent("primary_hand_changed", function()
- self:UpdateMenuAttachment()
- end)
-
end
self.panel:AddCSSClasses("Visible")
@@ -253,7 +276,7 @@ function DebugMenu:ShowMenu()
debugMenuOpen = true
end, 0.2)
- local panelHeight = Clamp(Convars:GetInt("alyxlib_debug_menu_height"), 7, 30)
+ local panelHeight = Clamp(Convars:GetInt("debug_menu_height"), 7, 30)
local cssHeight = (64 * panelHeight) - 111
Panorama:Send(self.panel, "SetHeight", cssHeight)
From cd05aece92991f751c6cad5031a89516c6259ed8 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Fri, 19 Sep 2025 23:49:37 +1200
Subject: [PATCH 051/146] allow RegisterAlyxLibConvar initializer function
---
scripts/vscripts/alyxlib/debug/commands.lua | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/scripts/vscripts/alyxlib/debug/commands.lua b/scripts/vscripts/alyxlib/debug/commands.lua
index 395b17a..5c6859e 100644
--- a/scripts/vscripts/alyxlib/debug/commands.lua
+++ b/scripts/vscripts/alyxlib/debug/commands.lua
@@ -28,14 +28,14 @@ end
---Registers a new AlyxLib console variable.
---
---@param name string # Name of the convar that will be given in the console.
----@param defaultValue string # Default value of the convar.
+---@param defaultValue string|function # Default value of the convar or initializer function.
---@param helpText? string # Description of the convar.
---@param flags? integer # Flags for the convar.
---@param callback? fun(newVal:string, oldVal:string): any # Update function called after the value has been changed.
function RegisterAlyxLibConvar(name, defaultValue, helpText, flags, callback)
helpText = helpText or "[No description]"
flags = flags or 0
- if callback then
+ if callback or type(defaultValue) == "function" then
EasyConvars:RegisterConvar(name, defaultValue, helpText, flags, callback)
else
Convars:RegisterConvar(name, defaultValue, helpText, flags)
From dbae5ce684cdbba4f2116f7f76481a4211a04562 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Fri, 19 Sep 2025 23:50:45 +1200
Subject: [PATCH 052/146] fix wrong activation hand
---
scripts/vscripts/alyxlib/debug/debug_menu.lua | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index ecb48bd..b0efb75 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -764,9 +764,8 @@ function DebugMenu:StartListeningForMenuActivation()
timeSinceLastButtonPress = math.huge
end
- local hand = Convars:GetBool("alyxlib_debug_menu_hand") and Player.SecondaryHand or Player.PrimaryHand
-
- if Player:IsDigitalActionOnForHand(hand.Literal, DIGITAL_INPUT_TOGGLE_MENU) then
+ -- Menu button is always on secondary hand (seems to be)
+ if Player:IsDigitalActionOnForHand(Player.SecondaryHand.Literal, DIGITAL_INPUT_TOGGLE_MENU) then
if not buttonPressed then
buttonPressed = true
timeSinceLastButtonPress = Time()
From 8e2eb51ffde265ce5bef519282e18c8c838c31bc Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Fri, 19 Sep 2025 23:52:02 +1200
Subject: [PATCH 053/146] allow activation button to close menu
---
scripts/vscripts/alyxlib/debug/debug_menu.lua | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index b0efb75..dc38b11 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -302,9 +302,7 @@ function DebugMenu:CloseMenu()
Player:SetCoughHandEnabled(true)
- if Player.HMDAvatar then
- self:StartListeningForMenuActivation()
- else
+ if not Player.HMDAvatar or IsFakeVREnabled() then
SendToConsole("unbind r")
end
end
@@ -772,10 +770,12 @@ function DebugMenu:StartListeningForMenuActivation()
buttonPresses = buttonPresses + 1
if buttonPresses >= buttonPressesToActivate then
- self:ShowMenu()
buttonPresses = 0
- -- Stop think
- return nil
+ if self:IsOpen() then
+ self:CloseMenu()
+ else
+ self:ShowMenu()
+ end
end
end
else
From a6291966193660495acf9d0114fc31dd49333261 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Sat, 20 Sep 2025 00:01:05 +1200
Subject: [PATCH 054/146] add menu dragging
---
.../layout/custom_game/alyxlib_debug_menu.xml | 2 +-
.../scripts/custom_game/alyxlib_debug_menu.js | 38 +++---
scripts/vscripts/alyxlib/debug/debug_menu.lua | 110 ++++++++++++++++--
3 files changed, 125 insertions(+), 25 deletions(-)
diff --git a/panorama/layout/custom_game/alyxlib_debug_menu.xml b/panorama/layout/custom_game/alyxlib_debug_menu.xml
index fcebabb..ead77a5 100644
--- a/panorama/layout/custom_game/alyxlib_debug_menu.xml
+++ b/panorama/layout/custom_game/alyxlib_debug_menu.xml
@@ -21,7 +21,7 @@
-
+
diff --git a/panorama/scripts/custom_game/alyxlib_debug_menu.js b/panorama/scripts/custom_game/alyxlib_debug_menu.js
index c85d720..10b0dc4 100644
--- a/panorama/scripts/custom_game/alyxlib_debug_menu.js
+++ b/panorama/scripts/custom_game/alyxlib_debug_menu.js
@@ -43,13 +43,17 @@ let currentlySelectedCategory = null;
*/
let currentlyActiveButton = null;
-function TurnButtonIntoDebugMenuButton(button, callback)
+function TurnButtonIntoDebugMenuButton(button, callback, onmouseover, onmouseout)
{
if (button == null) return;
- button.SetPanelEvent("onmouseover", () => currentlyActiveButton = button);
+ button.SetPanelEvent("onmouseover", () => {
+ currentlyActiveButton = button
+ if (onmouseover !== undefined) onmouseover();
+ });
button.SetPanelEvent("onmouseout", () => {
if (currentlyActiveButton == button) currentlyActiveButton = null;
+ if (onmouseout !== undefined) onmouseout();
});
if (callback !== null && callback !== undefined)
@@ -922,6 +926,11 @@ function CloseMenu()
FireOutput("_CloseMenu");
}
+function StartPanelDrag()
+{
+ FireOutput("_DebugMenuDrag");
+}
+
/**
* Gets the position of the left or right 'affordance' circle for the VR finger interacting with the menu.
* @returns {{x:number,y:number}?}
@@ -1228,24 +1237,25 @@ function ScrollHelperClick() {
{
// Modify preset layout buttons to work with controller trigger
TurnButtonIntoDebugMenuButton($("#CloseMenuButton"));
+ TurnButtonIntoDebugMenuButton($("#TitleBar"));
TurnButtonIntoDebugMenuButton($("#CycleCategoryLeftButton"));
TurnButtonIntoDebugMenuButton($("#CycleCategoryRightButton"));
- TurnButtonIntoDebugMenuButton($("#ScrollHelperDown"));
- TurnButtonIntoDebugMenuButton($("#ScrollHelperUp"));
+ // Scroll helpers for sub-menus
+ // Valve kindly didn't allow us to raytrace click panels like the main menu
+ // so this is a work around for scrolling
+ TurnButtonIntoDebugMenuButton($("#ScrollHelperDown"), ScrollHelperClick, () => StartScrollHelper("ScrollDown"), () => StopScrollHelper());
+ TurnButtonIntoDebugMenuButton($("#ScrollHelperUp"), ScrollHelperClick, () => StartScrollHelper("ScrollUp"), () => StopScrollHelper());
// Tells Lua that the menu has been reloaded so it can repopulate the menu
// This helps with hot reloading panel changes
- $.Schedule(0.1, () => FireOutput("_DebugMenuReloaded"));
+ if ($.GetContextPanel().initialized) {
+ $.Msg("Refreshing debug menu due to hot reload");
+ $.Schedule(0.1, () => FireOutput("_DebugMenuReloaded"));
+ }
- // Scroll helpers for sub-menus
- // Valve kindly didn't allow us to raytrace click panels like the main menu
- // so this is a work around for scrolling
- $('#ScrollHelperDown').SetPanelEvent("onmouseover", () => StartScrollHelper("ScrollDown"));
- $('#ScrollHelperDown').SetPanelEvent("onmouseout", () => StopScrollHelper());
- $('#ScrollHelperDown').SetPanelEvent("onactivate", ScrollHelperClick);
- $('#ScrollHelperUp').SetPanelEvent("onmouseover", () => StartScrollHelper("ScrollUp"));
- $('#ScrollHelperUp').SetPanelEvent("onmouseout", () => StopScrollHelper());
- $('#ScrollHelperUp').SetPanelEvent("onactivate", ScrollHelperClick);
+ // Set so the panel doesn't refresh when it's first created
+ // Only when modified and recompiled
+ $.GetContextPanel().initialized = true;
$.Schedule(1.0, () => panelReady = true);
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index dc38b11..bf2adec 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -9,7 +9,14 @@ RegisterAlyxLibCommand("debug_menu_show", function (name, ...)
DebugMenu:ShowMenu()
end, "Forces the debug menu to show")
-RegisterAlyxLibConvar("debug_menu_hand", "0", "Hand to attach the debug menu to, 0 = Secondary : 1 = Primary")
+RegisterAlyxLibConvar("debug_menu_hand", "1", "Hand to attach the debug menu to, 0 = Secondary : 1 = Primary")
+
+RegisterAlyxLibConvar("debug_menu_offset_x", "4", "X offset of the debug menu", 0)
+RegisterAlyxLibConvar("debug_menu_offset_y", "-9", "Y offset of the debug menu", 0)
+RegisterAlyxLibConvar("debug_menu_offset_z", "0", "Z offset of the debug menu", 0)
+RegisterAlyxLibConvar("debug_menu_offset_pitch", "0", "Pitch offset of the debug menu", 0)
+RegisterAlyxLibConvar("debug_menu_offset_yaw", "180", "Yaw offset of the debug menu", 0)
+RegisterAlyxLibConvar("debug_menu_offset_roll", "0", "Roll offset of the debug menu", 0)
RegisterAlyxLibConvar("debug_menu_height", "14", "Height of the debug menu, min=7 : max=30", 0, function(newVal, oldVal)
if DebugMenu:IsOpen() then
@@ -81,6 +88,87 @@ local function empty(str)
return type(str) ~= "string" or str == ""
end
+---@param dragHand CPropVRHand
+local function startDraggingMenu(dragHand)
+
+ local panel = DebugMenu.panel
+ local dragent = dragHand
+
+ local relpos = dragent:TransformPointWorldToEntity(panel:GetOrigin())
+
+ local panelOrigin = panel:GetOrigin()
+ local relForwardRef = dragent:TransformPointWorldToEntity(panelOrigin + panel:GetForwardVector() * 10)
+ local relUpRef = dragent:TransformPointWorldToEntity(panelOrigin + panel:GetUpVector() * 10)
+
+ panel:SetParent(GetWorld(), nil)
+
+ Player:QuickThink(function()
+ if not IsValidEntity(dragHand) or
+ not IsValidEntity(panel) then
+ return nil
+ end
+
+ if not Player:IsDigitalActionOnForHand(dragHand.Literal, DIGITAL_INPUT_MENU_INTERACT) then
+ local hand = Convars:GetBool("debug_menu_hand") and Player.PrimaryHand or Player.SecondaryHand
+ panel:SetParent(hand, "constraint1")
+
+ local localOrigin = panel:GetLocalOrigin()
+ local localAngles = panel:GetLocalAngles()
+
+ if hand == Player.LeftHand then
+ localAngles.y = localAngles.y - 180
+ localOrigin.y = -localOrigin.y
+ end
+
+ Convars:SetFloat("debug_menu_offset_x", localOrigin.x)
+ Convars:SetFloat("debug_menu_offset_y", localOrigin.y)
+ Convars:SetFloat("debug_menu_offset_z", localOrigin.z)
+ Convars:SetFloat("debug_menu_offset_pitch", localAngles.x)
+ Convars:SetFloat("debug_menu_offset_yaw", localAngles.y)
+ Convars:SetFloat("debug_menu_offset_roll", localAngles.z)
+
+ Msg("\n")
+ Msg("Debug menu offsets updated:\n")
+ Msg("\ndebug_menu_offset_x " .. math.trunc(Convars:GetFloat("debug_menu_offset_x"), 2))
+ Msg("\ndebug_menu_offset_y " .. math.trunc(Convars:GetFloat("debug_menu_offset_y"), 2))
+ Msg("\ndebug_menu_offset_z " .. math.trunc(Convars:GetFloat("debug_menu_offset_z"), 2))
+ Msg("\ndebug_menu_offset_pitch " .. math.trunc(Convars:GetFloat("debug_menu_offset_pitch"), 2))
+ Msg("\ndebug_menu_offset_yaw " .. math.trunc(Convars:GetFloat("debug_menu_offset_yaw"), 2))
+ Msg("\ndebug_menu_offset_roll " .. math.trunc(Convars:GetFloat("debug_menu_offset_roll"), 2))
+ Msg("\n\n")
+
+ DebugMenu:UpdateMenuAttachment()
+
+ return nil
+ end
+
+ local newOrigin = dragent:TransformPointEntityToWorld(relpos)
+
+ local newForwardRef = dragent:TransformPointEntityToWorld(relForwardRef)
+ local newUpRef = dragent:TransformPointEntityToWorld(relUpRef)
+
+ local newForward = (newForwardRef - newOrigin):Normalized()
+ local newUp = (newUpRef - newOrigin):Normalized()
+ local angles = VectorToAngles(newForward)
+
+ -- Calculate roll manually
+ local expectedRight = newForward:Cross(Vector(0, 0, 1)):Normalized()
+ if expectedRight:Length() < 0.1 then
+ expectedRight = Vector(1, 0, 0)
+ end
+ local expectedUp = expectedRight:Cross(newForward):Normalized()
+
+ local roll = math.atan2(newUp:Dot(expectedRight), newUp:Dot(expectedUp))
+ angles.z = Rad2Deg(roll)
+
+ panel:SetOrigin(newOrigin)
+ panel:SetQAngle(angles)
+
+ return 0
+ end)
+
+end
+
---
---The scope of the debug menu script.
---
@@ -178,6 +266,10 @@ local debugPanelScriptScope = {
if DebugMenu:IsOpen() then
DebugMenu:Refresh()
end
+ end,
+
+ _DebugMenuDrag = function()
+ startDraggingMenu(Convars:GetInt("debug_menu_hand") == 1 and Player.SecondaryHand or Player.PrimaryHand)
end
}
@@ -188,14 +280,12 @@ function DebugMenu:UpdateMenuAttachment()
local hand = Convars:GetBool("debug_menu_hand") and Player.PrimaryHand or Player.SecondaryHand
if hand == Player.RightHand then
self.panel:SetParent(hand, "constraint1")
- self.panel:ResetLocal()
- self.panel:SetLocalAngles(0, 180, 0)
- self.panel:SetLocalOrigin(Vector(4, -9, 0))
+ self.panel:SetLocalAngles(Convars:GetFloat("debug_menu_offset_pitch"), Convars:GetFloat("debug_menu_offset_yaw"), Convars:GetFloat("debug_menu_offset_roll"))
+ self.panel:SetLocalOrigin(Vector(Convars:GetFloat("debug_menu_offset_x"), Convars:GetFloat("debug_menu_offset_y"), Convars:GetFloat("debug_menu_offset_z")))
else
self.panel:SetParent(hand, "constraint1")
- self.panel:ResetLocal()
- self.panel:SetLocalAngles(0, 0, 0)
- self.panel:SetLocalOrigin(Vector(4, 9, 0))
+ self.panel:SetLocalAngles(Convars:GetFloat("debug_menu_offset_pitch"), Convars:GetFloat("debug_menu_offset_yaw")-180, Convars:GetFloat("debug_menu_offset_roll"))
+ self.panel:SetLocalOrigin(Vector(Convars:GetFloat("debug_menu_offset_x"), -Convars:GetFloat("debug_menu_offset_y"), Convars:GetFloat("debug_menu_offset_z")))
end
end
@@ -227,6 +317,8 @@ function DebugMenu:ShowMenu()
a = RotateOrientation(a, QAngle(0,-90,90))
self.panel:SetQAngle(a)
self.panel:SetOrigin(eyePos + dir * 16)
+ -- Panel must be parented for moving during drag to work
+ self.panel:SetParent(GetWorld(), nil)
SendToConsole("bind r _debug_menu_test_button_press")
else
@@ -272,9 +364,7 @@ function DebugMenu:ShowMenu()
Panorama:InitPanel(self.panel, "alyxlib_debug_menu")
- self.panel:Delay(function()
- debugMenuOpen = true
- end, 0.2)
+ debugMenuOpen = true
local panelHeight = Clamp(Convars:GetInt("debug_menu_height"), 7, 30)
local cssHeight = (64 * panelHeight) - 111
From 80293e8f912e4d1f14be603a66552811c64105b7 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Sat, 20 Sep 2025 00:01:21 +1200
Subject: [PATCH 055/146] add r_cubemap_debug_colors to extras
---
scripts/vscripts/alyxlib/debug/debug_menu_extras.lua | 2 ++
1 file changed, 2 insertions(+)
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu_extras.lua b/scripts/vscripts/alyxlib/debug/debug_menu_extras.lua
index e1449d0..37208ee 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu_extras.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu_extras.lua
@@ -23,6 +23,8 @@ DebugMenu:AddToggle(categoryId, "fullbright", "Fullbright", "mat_fullbright")
DebugMenu:AddToggle(categoryId, "visibility", "Vis", "vis_enable")
+DebugMenu:AddToggle(categoryId, "cubemapcolors", "Cubemap Colors", "r_cubemap_debug_colors")
+
DebugMenu:AddToggle(categoryId, categoryId.."_visfreeze", "Freeze Vis", nil, function(on)
if on then
SendToConsole("vis_debug_show 1")
From d3643a9624c1650978cb6002f3047ad51d31094f Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Sat, 20 Sep 2025 00:10:44 +1200
Subject: [PATCH 056/146] add debug_menu_lock
---
scripts/vscripts/alyxlib/debug/debug_menu.lua | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index bf2adec..8504a25 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -34,6 +34,8 @@ RegisterAlyxLibConvar("debug_menu_floating", function()
return false
end, "Menu will float in world instead of attached to hand")
+RegisterAlyxLibConvar("debug_menu_lock", "0", "Prevents the debug menu from being repositioned by the player", 0)
+
---
---The debug menu allows for easier VR testing by offering a customizable in-game menu.
---
@@ -269,7 +271,9 @@ local debugPanelScriptScope = {
end,
_DebugMenuDrag = function()
- startDraggingMenu(Convars:GetInt("debug_menu_hand") == 1 and Player.SecondaryHand or Player.PrimaryHand)
+ if not Convars:GetBool("debug_menu_lock") then
+ startDraggingMenu(Convars:GetInt("debug_menu_hand") == 1 and Player.SecondaryHand or Player.PrimaryHand)
+ end
end
}
From 76b5d9d1adc9ddd144e06283030fa936b0b354ef Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Sat, 20 Sep 2025 00:13:24 +1200
Subject: [PATCH 057/146] add debug_menu_extras
---
scripts/vscripts/alyxlib/debug/debug_menu.lua | 32 +++++++++++--------
.../alyxlib/debug/debug_menu_extras.lua | 5 ++-
2 files changed, 23 insertions(+), 14 deletions(-)
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index 8504a25..0cb9c6a 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -36,6 +36,8 @@ end, "Menu will float in world instead of attached to hand")
RegisterAlyxLibConvar("debug_menu_lock", "0", "Prevents the debug menu from being repositioned by the player", 0)
+RegisterAlyxLibConvar("debug_menu_extras", "0", "Enable the extras tab by default", 0)
+
---
---The debug menu allows for easier VR testing by offering a customizable in-game menu.
---
@@ -972,18 +974,22 @@ DebugMenu:AddButton(categoryId, "demo_recording", "Start Recording Demo", functi
end
end)
-DebugMenu:AddSeparator(categoryId)
-
-DebugMenu:AddButton(categoryId, "enableextras", "Enable Extras Tab...", function()
- if package.loaded["alyxlib.debug.debug_menu_extras"] == nil then
- require "alyxlib.debug.debug_menu_extras"
- -- Update the panel immediately
- local id = "alyxlib_extras"
- DebugMenu:SendCategoryToPanel(DebugMenu:GetCategory(id))
- DebugMenu:SetCategoryIndex(id, 2)
- ---@TODO Allow disabling extras tab
- DebugMenu:SetItemText(categoryId, "enableextras", "Extras Tab Enabled!")
- end
-end)
+if Convars:GetBool("debug_menu_extras") then
+ require "alyxlib.debug.debug_menu_extras"
+else
+ DebugMenu:AddSeparator(categoryId)
+
+ DebugMenu:AddButton(categoryId, "enableextras", "Enable Extras Tab...", function()
+ if package.loaded["alyxlib.debug.debug_menu_extras"] == nil then
+ require "alyxlib.debug.debug_menu_extras"
+ -- Update the panel immediately
+ local id = "alyxlib_extras"
+ DebugMenu:SendCategoryToPanel(DebugMenu:GetCategory(id))
+ DebugMenu:SetCategoryIndex(id, 2)
+ ---@TODO Allow disabling extras tab
+ DebugMenu:SetItemText(categoryId, "enableextras", "Extras Tab Enabled!")
+ end
+ end)
+end
return DebugMenu.version
\ No newline at end of file
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu_extras.lua b/scripts/vscripts/alyxlib/debug/debug_menu_extras.lua
index 37208ee..89bdbf6 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu_extras.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu_extras.lua
@@ -7,7 +7,10 @@
local version = "v1.0.0"
-require "alyxlib.debug.debug_menu"
+-- Simple require can cause loop with debug_menu.lua
+if package.loaded["alyxlib.debug.debug_menu"] == nil then
+ require "alyxlib.debug.debug_menu"
+end
local categoryId = "alyxlib_extras"
From 0a1aee2c7bd21c43e7dd2a83b55276111813ebab Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Sat, 20 Sep 2025 00:31:13 +1200
Subject: [PATCH 058/146] fix drag for floating menu
---
scripts/vscripts/alyxlib/debug/debug_menu.lua | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index 0cb9c6a..bb32cd0 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -81,6 +81,10 @@ DebugMenu.categories = {}
local debugMenuOpen = false
local handChangedListener = nil
+---The last hand that clicked a button.
+---@type CPropVRHand?
+local lastClickHand = nil
+
---Command to test trace button presses
if not IsVREnabled() then
Convars:RegisterCommand("_debug_menu_test_button_press", function()
@@ -113,6 +117,10 @@ local function startDraggingMenu(dragHand)
end
if not Player:IsDigitalActionOnForHand(dragHand.Literal, DIGITAL_INPUT_MENU_INTERACT) then
+ if Convars:GetBool("debug_menu_floating") then
+ return nil
+ end
+
local hand = Convars:GetBool("debug_menu_hand") and Player.PrimaryHand or Player.SecondaryHand
panel:SetParent(hand, "constraint1")
@@ -273,8 +281,8 @@ local debugPanelScriptScope = {
end,
_DebugMenuDrag = function()
- if not Convars:GetBool("debug_menu_lock") then
- startDraggingMenu(Convars:GetInt("debug_menu_hand") == 1 and Player.SecondaryHand or Player.PrimaryHand)
+ if not Convars:GetBool("debug_menu_lock") and lastClickHand then
+ startDraggingMenu(lastClickHand)
end
end
}
@@ -356,6 +364,7 @@ function DebugMenu:ShowMenu()
handType,
DIGITAL_INPUT_MENU_INTERACT, 1,
function (context, params)
+ lastClickHand = params.hand
self:ClickHoveredButton()
end, self)
From fcd00f0d5e88a242923ebee978ee7bd6e29f6569 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Sat, 20 Sep 2025 00:31:47 +1200
Subject: [PATCH 059/146] simplify menu attachment logic
---
scripts/vscripts/alyxlib/debug/debug_menu.lua | 54 +++++++++----------
1 file changed, 24 insertions(+), 30 deletions(-)
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index bb32cd0..eda2442 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -291,15 +291,28 @@ local debugPanelScriptScope = {
---Updates the physical menu by attaching it to the correct hand.
---
function DebugMenu:UpdateMenuAttachment()
- local hand = Convars:GetBool("debug_menu_hand") and Player.PrimaryHand or Player.SecondaryHand
- if hand == Player.RightHand then
- self.panel:SetParent(hand, "constraint1")
- self.panel:SetLocalAngles(Convars:GetFloat("debug_menu_offset_pitch"), Convars:GetFloat("debug_menu_offset_yaw"), Convars:GetFloat("debug_menu_offset_roll"))
- self.panel:SetLocalOrigin(Vector(Convars:GetFloat("debug_menu_offset_x"), Convars:GetFloat("debug_menu_offset_y"), Convars:GetFloat("debug_menu_offset_z")))
+ if Convars:GetBool("debug_menu_floating") or not Player.HMDAvatar or IsFakeVREnabled() then
+ local player = Entities:GetLocalPlayer()
+ local eyePos = player:EyePosition()
+ local fDir = player:EyeAngles():Forward()
+ local fAng = VectorToAngles(fDir)
+ fAng = RotateOrientation(fAng, QAngle(0, -90, 90))
+ self.panel:SetQAngle(fAng)
+ self.panel:SetOrigin(eyePos + fDir * 16)
+
+ -- Panel must be parented for moving during drag to work
+ self.panel:SetParent(GetWorld(), nil)
else
- self.panel:SetParent(hand, "constraint1")
- self.panel:SetLocalAngles(Convars:GetFloat("debug_menu_offset_pitch"), Convars:GetFloat("debug_menu_offset_yaw")-180, Convars:GetFloat("debug_menu_offset_roll"))
- self.panel:SetLocalOrigin(Vector(Convars:GetFloat("debug_menu_offset_x"), -Convars:GetFloat("debug_menu_offset_y"), Convars:GetFloat("debug_menu_offset_z")))
+ local hand = Convars:GetBool("debug_menu_hand") and Player.PrimaryHand or Player.SecondaryHand
+ if hand == Player.RightHand then
+ self.panel:SetParent(hand, "constraint1")
+ self.panel:SetLocalAngles(Convars:GetFloat("debug_menu_offset_pitch"), Convars:GetFloat("debug_menu_offset_yaw"), Convars:GetFloat("debug_menu_offset_roll"))
+ self.panel:SetLocalOrigin(Vector(Convars:GetFloat("debug_menu_offset_x"), Convars:GetFloat("debug_menu_offset_y"), Convars:GetFloat("debug_menu_offset_z")))
+ else
+ self.panel:SetParent(hand, "constraint1")
+ self.panel:SetLocalAngles(Convars:GetFloat("debug_menu_offset_pitch"), Convars:GetFloat("debug_menu_offset_yaw")-180, Convars:GetFloat("debug_menu_offset_roll"))
+ self.panel:SetLocalOrigin(Vector(Convars:GetFloat("debug_menu_offset_x"), -Convars:GetFloat("debug_menu_offset_y"), Convars:GetFloat("debug_menu_offset_z")))
+ end
end
end
@@ -323,32 +336,13 @@ function DebugMenu:ShowMenu()
horizontal_align = "1",
})
- if not Player.HMDAvatar or IsFakeVREnabled() then
- local localPlayer = Entities:GetLocalPlayer()
- local eyePos = localPlayer:EyePosition()
- local dir = localPlayer:EyeAngles():Forward()
- local a = VectorToAngles(dir)
- a = RotateOrientation(a, QAngle(0,-90,90))
- self.panel:SetQAngle(a)
- self.panel:SetOrigin(eyePos + dir * 16)
- -- Panel must be parented for moving during drag to work
- self.panel:SetParent(GetWorld(), nil)
+ self:UpdateMenuAttachment()
+ if not Player.HMDAvatar or IsFakeVREnabled() then
SendToConsole("bind r _debug_menu_test_button_press")
else
local handType = -1
- if Convars:GetBool("debug_menu_floating") then
- local localPlayer = Entities:GetLocalPlayer()
- local eyePos = localPlayer:EyePosition()
- local dir = localPlayer:EyeAngles():Forward()
- local a = VectorToAngles(dir)
- a = RotateOrientation(a, QAngle(0,-90,90))
- self.panel:SetQAngle(a)
- self.panel:SetOrigin(eyePos + dir * 16)
- self.panel:SetParent(GetWorld(), nil)
- else
- self:UpdateMenuAttachment()
-
+ if not Convars:GetBool("debug_menu_floating") then
handType = Convars:GetInt("debug_menu_hand") == 1 and InputHandSecondary or InputHandPrimary
handChangedListener = ListenToPlayerEvent("primary_hand_changed", function()
From f5a6c213ed462bb3d9ae300f86c3749c5c029b93 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Sat, 27 Sep 2025 06:19:18 +1200
Subject: [PATCH 060/146] refresh menu to last tab on update
---
panorama/scripts/custom_game/alyxlib_debug_menu.js | 11 +++++++++--
scripts/vscripts/alyxlib/debug/debug_menu.lua | 2 +-
scripts/vscripts/alyxlib/debug/debug_menu_cfg.lua | 8 ++++++++
3 files changed, 18 insertions(+), 3 deletions(-)
diff --git a/panorama/scripts/custom_game/alyxlib_debug_menu.js b/panorama/scripts/custom_game/alyxlib_debug_menu.js
index 10b0dc4..d42f721 100644
--- a/panorama/scripts/custom_game/alyxlib_debug_menu.js
+++ b/panorama/scripts/custom_game/alyxlib_debug_menu.js
@@ -828,6 +828,7 @@ function SetCategoryVisible(id)
// category.AddClass("Visible");
category.SetVisible(true);
currentlySelectedCategory = category;
+ $.GetContextPanel().currentlySelectedCategory = category.id;
}
else
{
@@ -894,8 +895,14 @@ function CreateCategory(id, name)
categories.push(category);
- if (currentlySelectedCategory == null)
- SetCategoryVisible(category.id);
+ // Restore selected tab on refresh
+ if ($.GetContextPanel().currentlySelectedCategory) {
+ if ($.GetContextPanel().currentlySelectedCategory == category.id) {
+ SetCategoryVisible(category.id);
+ }
+ } else if (currentlySelectedCategory == null) {
+ SetCategoryVisible(category.id)
+ }
UpdateCategoryBarVisibility();
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index eda2442..2dea0d8 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -837,7 +837,7 @@ end
---Forces the debug menu panel to refresh by removing and re-adding all categories and items.
---
function DebugMenu:Refresh()
- if self.panel then
+ if self:IsOpen() then
self:ClearMenu()
self:SendCategoriesToPanel()
end
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu_cfg.lua b/scripts/vscripts/alyxlib/debug/debug_menu_cfg.lua
index 288467c..8f1770f 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu_cfg.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu_cfg.lua
@@ -12,6 +12,7 @@ local currentUserCategory = ""
RegisterAlyxLibCommand("debug_menu_add_category", function (_, name)
currentUserCategory = DoUniqueString("category")
DebugMenu:AddCategory(currentUserCategory, name or "New Category")
+ DebugMenu:Refresh()
end, "Adds a new category to the debug menu")
RegisterAlyxLibCommand("debug_menu_add_button", function (_, text, command)
@@ -19,6 +20,7 @@ RegisterAlyxLibCommand("debug_menu_add_button", function (_, text, command)
return warn("debug_menu_add_button: No category defined with debug_menu_add_category")
end
DebugMenu:AddButton(currentUserCategory, DoUniqueString("button"), text or "Button", command or "echo Button pressed")
+ DebugMenu:Refresh()
end, "Adds a new button to the current category")
RegisterAlyxLibCommand("debug_menu_add_toggle", function (_, text, commandOff, commandOn, default)
@@ -62,6 +64,7 @@ RegisterAlyxLibCommand("debug_menu_add_toggle", function (_, text, commandOff, c
end
DebugMenu:AddToggle(currentUserCategory, DoUniqueString("toggle"), text or "Toggle", convar, callback, default)
+ DebugMenu:Refresh()
end, "Adds a new toggle to the current category")
RegisterAlyxLibCommand("debug_menu_add_slider", function (_, text, command, min, max, ...)
@@ -93,6 +96,7 @@ RegisterAlyxLibCommand("debug_menu_add_slider", function (_, text, command, min,
assert(type(truncate) == "nil" or type(increment) == "number", "debug_menu_add_slider: increment must be a number")
DebugMenu:AddSlider(currentUserCategory, DoUniqueString("slider"), text or "Slider", command, min or 0, max or 1, isPercentage or false, truncate, increment, nil, default)
+ DebugMenu:Refresh()
end, "Adds a new slider to the current category")
RegisterAlyxLibCommand("debug_menu_add_cycle", function (_, title, ...)
@@ -131,6 +135,7 @@ RegisterAlyxLibCommand("debug_menu_add_cycle", function (_, title, ...)
DebugMenu:AddCycle(currentUserCategory, DoUniqueString("cycle"), title or "Cycle", nil, parsedValues, function (index, item, cycle)
SendToConsole(item.value)
end, default)
+ DebugMenu:Refresh()
end, "Adds a new cycle to the current category")
---@type string[]
@@ -213,6 +218,7 @@ RegisterAlyxLibCommand("debug_menu_add_cycle_map", function(_, title, ...)
end, default)
end
+ DebugMenu:Refresh()
end, "Adds a new cycle to the current category using previously defined commands")
RegisterAlyxLibCommand("debug_menu_add_label", function (_, text)
@@ -221,6 +227,7 @@ RegisterAlyxLibCommand("debug_menu_add_label", function (_, text)
end
DebugMenu:AddLabel(currentUserCategory, DoUniqueString("label"), text)
+ DebugMenu:Refresh()
end, "Adds a new label to the current category")
RegisterAlyxLibCommand("debug_menu_add_separator", function (_, text)
@@ -229,6 +236,7 @@ RegisterAlyxLibCommand("debug_menu_add_separator", function (_, text)
end
DebugMenu:AddSeparator(currentUserCategory, nil, text)
+ DebugMenu:Refresh()
end, "Adds a new separator to the current category")
---Execute the debug_menu.cfg file
From 38a059248fcb8237323752479afbf6171aa7efff Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Sat, 27 Sep 2025 07:30:12 +1200
Subject: [PATCH 061/146] add item visibility condition
---
scripts/vscripts/alyxlib/debug/debug_menu.lua | 99 +++++++++++++------
1 file changed, 71 insertions(+), 28 deletions(-)
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index 2dea0d8..5d97951 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -70,6 +70,7 @@ DebugMenu.version = "v1.0.0"
---@field values {text:string,value:any}[] # Text/value pairs for this cycler.
---@field truncate number # The number of decimal places to truncate the slider value to (-1 for no truncating).
---@field increment number # The increment value to snap the slider value to (0 for no snapping).
+---@field condition? string|fun():boolean # The condition that must be met for this item to be visible.
---The panel entity.
---@type CPointClientUIWorldPanel
@@ -430,12 +431,15 @@ end
---Get a debug menu item by id.
---
---@param id string # The item ID
+---@param categoryId? string # Optionally specify a category to look in. If not specified, will look in all categories
---@return DebugMenuItem? # The item if it exists
-function DebugMenu:GetItem(id)
+function DebugMenu:GetItem(id, categoryId)
for _, category in ipairs(self.categories) do
- for _, item in ipairs(category.items) do
- if item.id == id then
- return item
+ if not categoryId or category.id == categoryId then
+ for _, item in ipairs(category.items) do
+ if item.id == id then
+ return item
+ end
end
end
end
@@ -770,39 +774,51 @@ function DebugMenu:SendCategoryToPanel(category)
Panorama:Send(panel, "AddCategory", category.id, category.name)
for _, item in ipairs(category.items) do
- if item.type == "button" then
- Panorama:Send(panel, "AddButton", item.categoryId, item.id, item.text)
- elseif item.type == "toggle" then
- Panorama:Send(panel, "AddToggle", item.categoryId, item.id, item.text, resolveDefault(item, Convars.GetBool, false))
+ local conditionMet = true
+ if item.condition then
+ if type(item.condition) == "string" then
+ conditionMet = Convars:GetBool(item.condition)
+ elseif type(item.condition) == "function" then
+ conditionMet = item.condition()
+ end
+ end
+
+ if conditionMet then
+ if item.type == "button" then
+ Panorama:Send(panel, "AddButton", item.categoryId, item.id, item.text)
+
+ elseif item.type == "toggle" then
+ Panorama:Send(panel, "AddToggle", item.categoryId, item.id, item.text, resolveDefault(item, Convars.GetBool, false))
- elseif item.type == "label" then
- Panorama:Send(panel, "AddLabel", item.categoryId, item.id, item.text)
+ elseif item.type == "label" then
+ Panorama:Send(panel, "AddLabel", item.categoryId, item.id, item.text)
- elseif item.type == "separator" then
- Panorama:Send(panel, "AddSeparator", item.categoryId, item.id, item.text)
+ elseif item.type == "separator" then
+ Panorama:Send(panel, "AddSeparator", item.categoryId, item.id, item.text)
- elseif item.type == "slider" then
- local default = resolveDefault(item, Convars.GetFloat, item.min)
- Panorama:Send(panel, "AddSlider", item.categoryId, item.id, item.text or item.convar, item.convar, item.min, item.max, default, item.isPercentage, item.truncate, item.increment)
+ elseif item.type == "slider" then
+ local default = resolveDefault(item, Convars.GetFloat, item.min)
+ Panorama:Send(panel, "AddSlider", item.categoryId, item.id, item.text or item.convar, item.convar, item.min, item.max, default, item.isPercentage, item.truncate, item.increment)
- elseif item.type == "cycle" then
+ elseif item.type == "cycle" then
- local default = resolveDefault(item)
+ local default = resolveDefault(item)
- if default ~= nil then
- -- find the index of the default value
- local index = TableFindIndex(item.values, function(v) return tostring(v.value) == tostring(default) end)
- if index > 0 then
- default = index
- else
- default = nil
+ if default ~= nil then
+ -- find the index of the default value
+ local index = TableFindIndex(item.values, function(v) return tostring(v.value) == tostring(default) end)
+ if index > 0 then
+ default = index
+ else
+ default = nil
+ end
end
- end
- Panorama:Send(panel, "AddCycle", item.categoryId, item.id, item.convar, item.text, default, TablePluck(item.values, "text"))
- else
- warn("Unknown item type '"..item.type.."'")
+ Panorama:Send(panel, "AddCycle", item.categoryId, item.id, item.convar, item.text, default, TablePluck(item.values, "text"))
+ else
+ warn("Unknown debug menu item type '"..item.type.."'")
+ end
end
end
end
@@ -843,6 +859,33 @@ function DebugMenu:Refresh()
end
end
+---
+---Sets the visibility condition for an item.
+---
+---If the condition is not met when the menu opens, the item will not appear in the menu.
+---
+---@param categoryId string # The category ID
+---@param itemId string # The item ID
+---@param condition string|fun():boolean|nil # Convar name, function, or `nil` to remove the condition
+---@overload fun(self: DebugMenu, item: DebugMenuItem, condition: string|fun():boolean|nil)
+function DebugMenu:SetItemVisibilityCondition(categoryId, itemId, condition)
+ ---@type DebugMenuItem
+ local item
+ if type(categoryId) == "table" then
+ item = categoryId
+ condition = itemId
+ else
+ item = self:GetItem(itemId, categoryId)
+ end
+
+ if not item then
+ warn("Cannot set item visibility condition '"..itemId.."': Item does not exist!")
+ return
+ end
+
+ item.condition = condition
+end
+
---
---Starts listening for the debug menu activation button.
---
From 269270f03af652800e467c18f94da080fb1e6fb9 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Sat, 27 Sep 2025 07:53:59 +1200
Subject: [PATCH 062/146] add entfind
---
scripts/vscripts/alyxlib/debug/common.lua | 2 ++
1 file changed, 2 insertions(+)
diff --git a/scripts/vscripts/alyxlib/debug/common.lua b/scripts/vscripts/alyxlib/debug/common.lua
index 09fa13d..77115f5 100644
--- a/scripts/vscripts/alyxlib/debug/common.lua
+++ b/scripts/vscripts/alyxlib/debug/common.lua
@@ -55,6 +55,8 @@ function Debug.FindEntityByPattern(pattern, exact)
return ent
end
+---@diagnostic disable-next-line: lowercase-global
+entfind = Debug.FindEntityByPattern
---
---Finds all entities whose name, class or model match `pattern`.
From 2421089d7f97c9b093ec4b1f1d0c2544a8cb33b9 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Sat, 27 Sep 2025 07:54:13 +1200
Subject: [PATCH 063/146] add entspawn
---
scripts/vscripts/alyxlib/debug/common.lua | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/scripts/vscripts/alyxlib/debug/common.lua b/scripts/vscripts/alyxlib/debug/common.lua
index 77115f5..b8464c7 100644
--- a/scripts/vscripts/alyxlib/debug/common.lua
+++ b/scripts/vscripts/alyxlib/debug/common.lua
@@ -970,4 +970,18 @@ function Debug.TableStr(tbl)
return "{ "..table.concat(vals, ", ") .. " }"
end
+---
+---Spawns an entity synchronously.
+---
+---@param classname string # The classname of the entity
+---@param spawnkeys? table|string # The spawnkeys table or targetname
+---@return EntityHandle
+---@diagnostic disable-next-line: lowercase-global
+function entspawn(classname, spawnkeys)
+ if type(spawnkeys) == "string" then
+ spawnkeys = { targetname = spawnkeys }
+ end
+ return SpawnEntityFromTableSynchronous(classname, spawnkeys or {})
+end
+
return Debug.version
\ No newline at end of file
From 2a9c4599ac49c7d5debde24c2c633632a05b00b1 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Sat, 27 Sep 2025 07:55:45 +1200
Subject: [PATCH 064/146] add IsAddonEnabled
---
scripts/vscripts/alyxlib/globals.lua | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/scripts/vscripts/alyxlib/globals.lua b/scripts/vscripts/alyxlib/globals.lua
index 6bd0c38..59910e6 100644
--- a/scripts/vscripts/alyxlib/globals.lua
+++ b/scripts/vscripts/alyxlib/globals.lua
@@ -150,6 +150,15 @@ function GetEnabledAddons()
return addons
end
+---
+---Checks if the addon with the given `workshopID` is enabled.
+---
+---@param workshopID string # The workshop ID of the addon
+---@return boolean # `true` if the addon is enabled, `false` otherwise
+function IsAddonEnabled(workshopID)
+ return vlua.find(GetEnabledAddons(), workshopID)
+end
+
---
---Get if the given `handle` value is an entity, regardless of if it's still alive.
---
From ee388c9c8fd4a11c38f05b6d396e0b55e08403e9 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Sat, 27 Sep 2025 08:19:38 +1200
Subject: [PATCH 065/146] add RandomChance
---
scripts/vscripts/alyxlib/globals.lua | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/scripts/vscripts/alyxlib/globals.lua b/scripts/vscripts/alyxlib/globals.lua
index 59910e6..916b87f 100644
--- a/scripts/vscripts/alyxlib/globals.lua
+++ b/scripts/vscripts/alyxlib/globals.lua
@@ -1117,4 +1117,24 @@ function SetPhysVelocity(entity, velocity)
entity:SetAbsVelocity(velocity)
end
+---
+---Returns one of two values based on a percentage chance.
+---
+---If the random roll succeeds, returns `onTrue` (default: true).
+---If it fails, returns `onFalse` (default: false).
+---
+---@param chance number # The percentage chance of success (0-100).
+---@param onTrue? any # Value to return if the chance succeeds (default: true).
+---@param onFalse? any # Value to return if the chance fails (default: false).
+---@return boolean|any
+function RandomChance(chance, onTrue, onFalse)
+ if onTrue == nil then onTrue = true end
+ if onFalse == nil then onFalse = false end
+ if RandomFloat(0, 100) <= chance then
+ return onTrue
+ else
+ return onFalse
+ end
+end
+
return _version
\ No newline at end of file
From cba937d5b3673ca0624e4a5073c0f8730c01a530 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Sat, 27 Sep 2025 08:23:02 +1200
Subject: [PATCH 066/146] update readme
---
README.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index cb0f833..0581191 100644
--- a/README.md
+++ b/README.md
@@ -31,11 +31,12 @@ Your addon is linked to AlyxLib via symbolic links, ensuring that every addon us
* Full VScript code completion using [Lua Language Server](https://luals.github.io/)
* Save/Load most data types easily to any entity.
-* Custom class implementation for entities, including inheritence and automatic variable saving.
+* Custom class implementation for entities, including inheritance and automatic variable saving.
* Player interaction simplification and tracking of items.
* Panorama panel interaction, allowing sending and receiving data with Lua.
* Easy controller input tracking with function callbacks.
* Lots of useful debugging functions and console commands.
+* A fully customizable in-game debug menu.
See the extensive wiki for full function reference and code examples.
From f0e8c81378970e7a7ff670fa4b43322b88dff67c Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Sat, 27 Sep 2025 08:24:17 +1200
Subject: [PATCH 067/146] noclip_vr_enabled accessible outside vr
---
scripts/vscripts/alyxlib/debug/commands.lua | 3 +++
scripts/vscripts/alyxlib/debug/vr.lua | 3 ---
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/scripts/vscripts/alyxlib/debug/commands.lua b/scripts/vscripts/alyxlib/debug/commands.lua
index 5c6859e..932ddda 100644
--- a/scripts/vscripts/alyxlib/debug/commands.lua
+++ b/scripts/vscripts/alyxlib/debug/commands.lua
@@ -639,4 +639,7 @@ local symbols = {"and","break","do","else","elseif","end","false","for","functio
-- end
+---Hidden convar state that can be retrieved by other scripts
+Convars:RegisterConvar("noclip_vr_enabled", "0", "True if noclip_vr is enabled (readonly)", FCVAR_HIDDEN)
+
return version
\ No newline at end of file
diff --git a/scripts/vscripts/alyxlib/debug/vr.lua b/scripts/vscripts/alyxlib/debug/vr.lua
index f5cc1af..f01fbb2 100644
--- a/scripts/vscripts/alyxlib/debug/vr.lua
+++ b/scripts/vscripts/alyxlib/debug/vr.lua
@@ -114,9 +114,6 @@ RegisterAlyxLibCommand("hlvr_give_grabbity_gloves", function (_)
end
end, "Gives the player grabbity gloves", FCVAR_NONE)
----Hidden convar state that can be retrieved by other scripts
-RegisterAlyxLibConvar("noclip_vr_enabled", "0", "True if noclip_vr is enabled (readonly)", FCVAR_HIDDEN)
-
---Tracks initial button press to prevent repeated logic execution while held
local quickTurnFlag = false
From d715fdbc9db4945d28b33211865d12dee9611542 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Sun, 28 Sep 2025 17:52:16 +1300
Subject: [PATCH 068/146] fix large profiler unpack error
---
scripts/vscripts/alyxlib/debug/profiling.lua | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/scripts/vscripts/alyxlib/debug/profiling.lua b/scripts/vscripts/alyxlib/debug/profiling.lua
index 32f1eba..146b22a 100644
--- a/scripts/vscripts/alyxlib/debug/profiling.lua
+++ b/scripts/vscripts/alyxlib/debug/profiling.lua
@@ -124,7 +124,7 @@ function profiler:GetMedian()
return 0
end
- local sortedValues = {unpack(self.measurements)}
+ local sortedValues = vlua.clone(self.measurements)
table.sort(sortedValues)
local count = #sortedValues
if count % 2 == 1 then
From ca38bcd882d5c6416529d8bea548a75d724a2b59 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Sun, 28 Sep 2025 17:53:04 +1300
Subject: [PATCH 069/146] slight call performance boost
---
scripts/vscripts/alyxlib/math/obb.lua | 29 +++++++++++++++------------
1 file changed, 16 insertions(+), 13 deletions(-)
diff --git a/scripts/vscripts/alyxlib/math/obb.lua b/scripts/vscripts/alyxlib/math/obb.lua
index 3aa417d..3588a92 100644
--- a/scripts/vscripts/alyxlib/math/obb.lua
+++ b/scripts/vscripts/alyxlib/math/obb.lua
@@ -11,6 +11,9 @@
local version = "v1.0.0"
+local _abs = math.abs
+local insert = table.insert
+
---
---Projects an OBB onto an axis.
---
@@ -22,9 +25,9 @@ local version = "v1.0.0"
local function projectOBB(centerLocal, half, origin, angles, axis)
local f, r, u = angles:Forward(), angles:Left(), angles:Up()
local centerWorld = origin + f*centerLocal.x + r*centerLocal.y + u*centerLocal.z
- local radius = half.x*math.abs(f:Dot(axis)) +
- half.y*math.abs(r:Dot(axis)) +
- half.z*math.abs(u:Dot(axis))
+ local radius = half.x*_abs(f:Dot(axis)) +
+ half.y*_abs(r:Dot(axis)) +
+ half.z*_abs(u:Dot(axis))
return centerWorld:Dot(axis) - radius, centerWorld:Dot(axis) + radius
end
@@ -81,17 +84,17 @@ function GetEntityAABB(entity)
+ u * data.center.z
-- half extents projected onto world axes
- local hx = math.abs(f.x) * data.half.x +
- math.abs(r.x) * data.half.y +
- math.abs(u.x) * data.half.z
+ local hx = _abs(f.x) * data.half.x +
+ _abs(r.x) * data.half.y +
+ _abs(u.x) * data.half.z
- local hy = math.abs(f.y) * data.half.x +
- math.abs(r.y) * data.half.y +
- math.abs(u.y) * data.half.z
+ local hy = _abs(f.y) * data.half.x +
+ _abs(r.y) * data.half.y +
+ _abs(u.y) * data.half.z
- local hz = math.abs(f.z) * data.half.x +
- math.abs(r.z) * data.half.y +
- math.abs(u.z) * data.half.z
+ local hz = _abs(f.z) * data.half.x +
+ _abs(r.z) * data.half.y +
+ _abs(u.z) * data.half.z
local halfWorld = Vector(hx, hy, hz)
@@ -132,7 +135,7 @@ function OBBvsOBB(obbDataA, originA, anglesA, obbDataB, originB, anglesB)
for _,b in ipairs({fB, rB, uB}) do
local cross = a:Cross(b)
if cross:Length() > 0.0001 then
- table.insert(axes, cross:Normalized())
+ insert(axes, cross:Normalized())
end
end
end
From 6f3598f8c782fbc7c2e8cb3120099ca097a28bba Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Sun, 28 Sep 2025 17:56:12 +1300
Subject: [PATCH 070/146] move menu with anchor parent
---
.../styles/custom_game/alyxlib_debug_menu.css | 7 ++
scripts/vscripts/alyxlib/debug/debug_menu.lua | 108 ++++++++++++++----
2 files changed, 91 insertions(+), 24 deletions(-)
diff --git a/panorama/styles/custom_game/alyxlib_debug_menu.css b/panorama/styles/custom_game/alyxlib_debug_menu.css
index 2e25a38..7506fa3 100644
--- a/panorama/styles/custom_game/alyxlib_debug_menu.css
+++ b/panorama/styles/custom_game/alyxlib_debug_menu.css
@@ -399,4 +399,11 @@ Label.custom_label
/* Make slider value fit even with many decimal places */
.slider_value {
text-overflow: shrink;
+}
+
+.InstantOpen .game_menu_root
+{
+ transition-property: none;
+ transition-duration: 0s;
+ transition-timing-function: ease-in;
}
\ No newline at end of file
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index 5d97951..dd4dbc6 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -97,17 +97,61 @@ local function empty(str)
return type(str) ~= "string" or str == ""
end
+-- Hoping these two functions find a way to be generalized later
+
+-- Convert position and orientation to entity's local coordinate system
+local function TransformToLocal(entity, position, angles)
+ local localPos = entity:TransformPointWorldToEntity(position)
+
+ -- Convert angles to local orientation using reference points
+ local origin = position
+ local forwardPoint = origin + angles:Forward() * 10
+ local upPoint = origin + angles:Up() * 10
+
+ local localForward = entity:TransformPointWorldToEntity(forwardPoint)
+ local localUp = entity:TransformPointWorldToEntity(upPoint)
+
+ return {
+ position = localPos,
+ forwardRef = localForward,
+ upRef = localUp,
+ }
+end
+
+-- Convert from entity's local coordinate system back to world coordinates
+local function TransformToWorld(entity, localTransform)
+ local worldPos = entity:TransformPointEntityToWorld(localTransform.position)
+
+ -- Reconstruct world orientation from reference points
+ local worldForwardRef = entity:TransformPointEntityToWorld(localTransform.forwardRef)
+ local worldUpRef = entity:TransformPointEntityToWorld(localTransform.upRef)
+
+ local worldForward = (worldForwardRef - worldPos):Normalized()
+ local worldUp = (worldUpRef - worldPos):Normalized()
+
+ -- Convert direction vectors back to angles
+ local angles = VectorToAngles(worldForward)
+
+ -- Calculate roll from up vector
+ local expectedRight = worldForward:Cross(Vector(0, 0, 1)):Normalized()
+ if expectedRight:Length() < 0.1 then
+ expectedRight = Vector(1, 0, 0)
+ end
+ local expectedUp = expectedRight:Cross(worldForward):Normalized()
+
+ local roll = math.atan2(worldUp:Dot(expectedRight), worldUp:Dot(expectedUp))
+ angles.z = Rad2Deg(roll)
+
+ return worldPos, angles
+end
+
---@param dragHand CPropVRHand
local function startDraggingMenu(dragHand)
local panel = DebugMenu.panel
local dragent = dragHand
- local relpos = dragent:TransformPointWorldToEntity(panel:GetOrigin())
-
- local panelOrigin = panel:GetOrigin()
- local relForwardRef = dragent:TransformPointWorldToEntity(panelOrigin + panel:GetForwardVector() * 10)
- local relUpRef = dragent:TransformPointWorldToEntity(panelOrigin + panel:GetUpVector() * 10)
+ local relativeTransform = TransformToLocal(dragHand, panel:GetOrigin(), panel:GetAngles())
panel:SetParent(GetWorld(), nil)
@@ -155,24 +199,7 @@ local function startDraggingMenu(dragHand)
return nil
end
- local newOrigin = dragent:TransformPointEntityToWorld(relpos)
-
- local newForwardRef = dragent:TransformPointEntityToWorld(relForwardRef)
- local newUpRef = dragent:TransformPointEntityToWorld(relUpRef)
-
- local newForward = (newForwardRef - newOrigin):Normalized()
- local newUp = (newUpRef - newOrigin):Normalized()
- local angles = VectorToAngles(newForward)
-
- -- Calculate roll manually
- local expectedRight = newForward:Cross(Vector(0, 0, 1)):Normalized()
- if expectedRight:Length() < 0.1 then
- expectedRight = Vector(1, 0, 0)
- end
- local expectedUp = expectedRight:Cross(newForward):Normalized()
-
- local roll = math.atan2(newUp:Dot(expectedRight), newUp:Dot(expectedUp))
- angles.z = Rad2Deg(roll)
+ local newOrigin, angles = TransformToWorld(dragHand, relativeTransform)
panel:SetOrigin(newOrigin)
panel:SetQAngle(angles)
@@ -288,6 +315,34 @@ local debugPanelScriptScope = {
end
}
+---Moves the panel with player's anchor parent so it doesn't get left behind on trains.
+---Parenting causes stutters so it must be calculated manually.
+---@param panel EntityHandle
+local function updatePanelWithAnchorParent(panel)
+ ---@type EntityHandle?
+ local currentAnchorParent = nil
+ local anchorRelativeTransform = nil
+
+ panel:SetContextThink("AnchorParentUpdate", function()
+ local parent = Player.HMDAnchor:GetMoveParent()
+ if parent ~= currentAnchorParent then
+ currentAnchorParent = Player.HMDAnchor:GetMoveParent()
+ if currentAnchorParent then
+ anchorRelativeTransform = TransformToLocal(currentAnchorParent, panel:GetOrigin(), panel:GetAngles())
+ end
+ end
+
+ if currentAnchorParent and anchorRelativeTransform then
+ local newPos, newAng = TransformToWorld(currentAnchorParent, anchorRelativeTransform)
+ panel:SetOrigin(newPos)
+ panel:SetQAngle(newAng)
+ return 0
+ end
+
+ return 0.5
+ end, 0)
+end
+
---
---Updates the physical menu by attaching it to the correct hand.
---
@@ -303,6 +358,8 @@ function DebugMenu:UpdateMenuAttachment()
-- Panel must be parented for moving during drag to work
self.panel:SetParent(GetWorld(), nil)
+
+ updatePanelWithAnchorParent(self.panel)
else
local hand = Convars:GetBool("debug_menu_hand") and Player.PrimaryHand or Player.SecondaryHand
if hand == Player.RightHand then
@@ -330,11 +387,14 @@ function DebugMenu:ShowMenu()
panel_dpi = 64,
ignore_input = 0,
lit = 0,
- interact_distance = 12,
+ interact_distance = 64,
vertical_align = "1",
-- orientation = "0",
horizontal_align = "1",
+
+ -- For some reason moving menus slow down css transitions
+ panel_class_name = Convars:GetBool("debug_menu_floating") and "InstantOpen" or ""
})
self:UpdateMenuAttachment()
From 5dd573b9d00cb04c9cdf7f18cac825ccf5bc453e Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Sun, 28 Sep 2025 19:29:47 +1300
Subject: [PATCH 071/146] add PointInAABB and PointInOBB
---
scripts/vscripts/alyxlib/math/obb.lua | 43 +++++++++++++++++++++++++++
1 file changed, 43 insertions(+)
diff --git a/scripts/vscripts/alyxlib/math/obb.lua b/scripts/vscripts/alyxlib/math/obb.lua
index 3588a92..0a194b7 100644
--- a/scripts/vscripts/alyxlib/math/obb.lua
+++ b/scripts/vscripts/alyxlib/math/obb.lua
@@ -176,6 +176,49 @@ function AABBvsOBB(aabbMin, aabbMax, obbData, obbOrigin, obbAngles)
)
end
+---
+---Tests if a point is inside an AABB.
+---
+---@param aMin Vector # The minimum corner of the AABB.
+---@param aMax Vector # The maximum corner of the AABB.
+---@param point Vector # The point to test.
+---@return boolean
+function PointInAABB(aMin, aMax, point)
+ return point.x >= aMin.x and point.x <= aMax.x and
+ point.y >= aMin.y and point.y <= aMax.y and
+ point.z >= aMin.z and point.z <= aMax.z
+end
+
+---
+---Tests if a point is inside an OBB.
+---
+---@param obbData OBBData # The OBB data (center, half extents in local space).
+---@param origin Vector # The world space origin of the OBB.
+---@param angles QAngle # The world space orientation of the OBB.
+---@param point Vector # The point to test.
+---@return boolean
+function PointInOBB(obbData, origin, angles, point)
+ local f, r, u = angles:Forward(), angles:Left(), angles:Up()
+
+ -- world position of the OBB’s local center
+ local center = origin
+ + f * obbData.center.x
+ + r * obbData.center.y
+ + u * obbData.center.z
+
+ -- vector from OBB center to point
+ local d = point - center
+
+ -- project onto each local axis and compare to half extents
+ local projF = d:Dot(f)
+ local projR = d:Dot(r)
+ local projU = d:Dot(u)
+
+ return math.abs(projF) <= obbData.half.x and
+ math.abs(projR) <= obbData.half.y and
+ math.abs(projU) <= obbData.half.z
+end
+
---
---Draws an OBB in the world.
---
From eaebde72ab1511e0b90d3bee4c833b55a66fcbbd Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Mon, 29 Sep 2025 14:16:15 +1300
Subject: [PATCH 072/146] improve
---
scripts/vscripts/alyxlib/extensions/entity.lua | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/scripts/vscripts/alyxlib/extensions/entity.lua b/scripts/vscripts/alyxlib/extensions/entity.lua
index f7ddc1b..b5f998c 100644
--- a/scripts/vscripts/alyxlib/extensions/entity.lua
+++ b/scripts/vscripts/alyxlib/extensions/entity.lua
@@ -494,7 +494,7 @@ end
---
---Quickly start a think function on the entity with a random name and no delay.
---
----@param func fun(...):number? # The think function.
+---@param func fun():number? # The think function.
---@param delay? number # Delay before starting the think.
---@return string # The name of the think for stopping later if desired.
function CBaseEntity:QuickThink(func, delay)
From a41b9272047531f5dbbd5606e1614786f80a0b29 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Mon, 29 Sep 2025 17:12:25 +1300
Subject: [PATCH 073/146] novr improvements
---
scripts/vscripts/alyxlib/debug/novr.lua | 112 ++++++++++++++++++------
1 file changed, 85 insertions(+), 27 deletions(-)
diff --git a/scripts/vscripts/alyxlib/debug/novr.lua b/scripts/vscripts/alyxlib/debug/novr.lua
index 480d82a..039fdc0 100644
--- a/scripts/vscripts/alyxlib/debug/novr.lua
+++ b/scripts/vscripts/alyxlib/debug/novr.lua
@@ -100,7 +100,7 @@ local usePressTime = 0
local HOLD_TIME = 0.8
local DISTANCE_WEIGHT = 1
-local LOS_WIGHT = 1.3
+local LOS_WEIGHT = 1.3
---Get text that should show for an entity.
---@param data NoVrInteractClass
@@ -155,32 +155,40 @@ local function activateEntity(entity, data)
debugoverlay:Text(getTextPosition(entity, data), 0, getText(data), 0, 50, 255, 50, 255, 2)
end
+-- index is different for fakevr
+-- 5 for novr, 35 for fakevr
+local fakeUseButton = IsFakeVREnabled() and 35 or 5
+
local function interactThink()
- ---@type EntityHandle
- local bestEnt = nil
---@type number
local bestScore = 0
+ ---@type EntityHandle?
+ local bestEnt = nil
+
---@type NoVrInteractClass
- local data = nil
+ local useData = nil
+
for _, interactData in ipairs(interactClasses) do
- -- local nearestEnt = Entities:FindByClassnameNearest(interactData.class, Player:EyePosition(), INTERACT_DISTANCE)
local nearestEnts = Entities:FindAllByClassnameWithin(interactData.class, Player:EyePosition(), INTERACT_DISTANCE)
for _, nearestEnt in ipairs(nearestEnts) do
if nearestEnt then
- local dist = VectorDistance(nearestEnt:GetOrigin(), Player:EyePosition())
- local normalizedDist = 1 - (math.min(dist / INTERACT_DISTANCE, 1))
- local dot = Player:EyeAngles():Forward():Dot((nearestEnt:GetAbsOrigin() - Player:EyePosition()):Normalized())
- local score = (normalizedDist * DISTANCE_WEIGHT) + (dot * LOS_WIGHT) + (interactData.weight or 1)
- -- debugoverlay:Text(nearestEnt:GetAbsOrigin(), 1, tostring(score) .. " " .. tostring(dot), 0, 255, 0, 0, 255, 0.1)
- if score > bestScore then
- bestEnt = nearestEnt
- bestScore = score
- data = interactData
+ local owner = nearestEnt:GetMoveParent() and nearestEnt:GetMoveParent():GetOwner()
+ if nearestEnt:GetName() ~= "Conform Helper" and (not owner or (owner:GetClassname() ~= "info_hlvr_equip_player" and owner:GetClassname() ~= "player")) then
+ local dist = VectorDistance(nearestEnt:GetOrigin(), Player:EyePosition())
+ local normalizedDist = 1 - (math.min(dist / INTERACT_DISTANCE, 1))
+ local dot = Player:EyeAngles():Forward():Dot((nearestEnt:GetAbsOrigin() - Player:EyePosition()):Normalized())
+ local score = (normalizedDist * DISTANCE_WEIGHT) + (dot * LOS_WEIGHT) + (interactData.weight or 1)
+ -- debugoverlay:Text(nearestEnt:GetAbsOrigin(), 1, tostring(score) .. " " .. tostring(dot), 0, 255, 0, 0, 255, 0.1)
+ if score > bestScore then
+ bestEnt = nearestEnt
+ bestScore = score
+ useData = interactData
+ end
end
end
@@ -189,24 +197,23 @@ local function interactThink()
end
if bestEnt then
- local text = getText(data)
- local pos = getTextPosition(bestEnt, data)
+ local text = getText(useData)
+ local pos = getTextPosition(bestEnt, useData)
debugoverlay:Text(pos, 0, text, 0, 255, 255, 255, 255, 0.1)
-
- if Player:IsVRControllerButtonPressed(5) then
+ if Player:IsVRControllerButtonPressed(fakeUseButton) then
if not isUsePressed then
isUsePressed = true
usePressTime = Time()
- if not data.hold then
- activateEntity(bestEnt, data)
+ if not useData.hold then
+ activateEntity(bestEnt, useData)
end
end
if (Time() - usePressTime) > HOLD_TIME then
- activateEntity(bestEnt, data)
+ activateEntity(bestEnt, useData)
usePressTime = math.huge
end
else
@@ -249,13 +256,15 @@ function NoVR:DisableAllDebugging()
Player:SetContextThink("novrInteractThink", nil, 0)
end
-ListenToPlayerEvent("novr_player", function (params)
- cl_forwardspeed = Convars:GetFloat("cl_forwardspeed")
- cl_backspeed = Convars:GetFloat("cl_backspeed")
- cl_sidespeed = Convars:GetFloat("cl_sidespeed")
+ListenToPlayerEvent("player_activate", function (params)
+ if not IsVREnabled() then
+ cl_forwardspeed = Convars:GetFloat("cl_forwardspeed")
+ cl_backspeed = Convars:GetFloat("cl_backspeed")
+ cl_sidespeed = Convars:GetFloat("cl_sidespeed")
- if NoVR.AutoStartInToolsMode and IsInToolsMode() then
- NoVR:EnableAllDebugging()
+ if NoVR.AutoStartInToolsMode and IsInToolsMode() then
+ NoVR:EnableAllDebugging()
+ end
end
end)
@@ -448,4 +457,53 @@ ListenToGameEvent("server_shutdown", function()
end
end, nil)
+Convars:RegisterCommand("alyxlib_start_print_novr_button_presses", function (_)
+ local player = Entities:GetLocalPlayer()
+ if not player then
+ warn("Cannot print novr buttons while player does not exist!")
+ return
+ end
+
+ local buttonsPressed = {}
+
+ player:SetContextThink("DebugPrintNoVRButtonPresses", function()
+
+ local msgPrinted = false
+ for i = 0, 100 do
+ if player:IsVRControllerButtonPressed(i) then
+ if not buttonsPressed[i] then
+ buttonsPressed[i] = true
+ Msg("NoVR Button: ".. i .."" .. " Pressed\n")
+ msgPrinted = true
+ end
+ else
+ if buttonsPressed[i] ~= nil then
+ buttonsPressed[i] = nil
+ Msg("NoVR Button: ".. i .."" .. " Released\n")
+ msgPrinted = true
+ end
+ end
+ end
+
+ if msgPrinted then
+ Msg("\n")
+ end
+
+ return 0
+ end, 0)
+
+ Msg("Started printing NoVR button presses. Use alyxlib_stop_print_novr_button_presses to stop...\n")
+end, "", 0)
+
+Convars:RegisterCommand("alyxlib_stop_print_novr_button_presses", function (_)
+ local player = Entities:GetLocalPlayer()
+ if not player then
+ warn("Cannot stop printing novr buttons while player does not exist!")
+ return
+ end
+
+ player:SetContextThink("DebugPrintNoVRButtonPresses", nil, 0)
+ Msg("Stopped printing novr button presses...\n")
+end, "", 0)
+
return version
\ No newline at end of file
From 27f9894e9b4d09de97ea56ee4674978d7d7796d4 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Wed, 8 Oct 2025 12:49:05 +1300
Subject: [PATCH 074/146] update gitignore
---
.gitignore | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/.gitignore b/.gitignore
index 61f8fcd..e66ec9a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,10 +1,16 @@
+# Source Engine
/_bakeresourcecache/
*bakeresourcecache.vpk
+
+# Tooling
__pycache__
/release/
/test_release/
__test*
*.bak
+
+# MkDocs
+.cache/
check_lua_versions.sh
-.cache/
\ No newline at end of file
+serve_mobile.bat
\ No newline at end of file
From 87a81968dd166f085a02fc944a14b152be5c4692 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Wed, 8 Oct 2025 13:01:52 +1300
Subject: [PATCH 075/146] fix critical input bug
StopListening was using the wrong table causing inputs to keep listening
forever this way
---
scripts/vscripts/alyxlib/controls/input.lua | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/scripts/vscripts/alyxlib/controls/input.lua b/scripts/vscripts/alyxlib/controls/input.lua
index 00432ec..aeb5e25 100644
--- a/scripts/vscripts/alyxlib/controls/input.lua
+++ b/scripts/vscripts/alyxlib/controls/input.lua
@@ -400,7 +400,7 @@ end
function Input:StopListening(id)
for _id, tbl in pairs(buttonCallbacks) do
if _id == id then
- tbl[_id] = nil
+ buttonCallbacks[_id] = nil
return
end
end
From 1bb0bf2120ebb4c92dc0ba48e09d13099686af1e Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Wed, 8 Oct 2025 13:03:52 +1300
Subject: [PATCH 076/146] draw sphere with no depth test
allows the sphere to be located through walls
---
scripts/vscripts/alyxlib/debug/commands.lua | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/scripts/vscripts/alyxlib/debug/commands.lua b/scripts/vscripts/alyxlib/debug/commands.lua
index 932ddda..9340051 100644
--- a/scripts/vscripts/alyxlib/debug/commands.lua
+++ b/scripts/vscripts/alyxlib/debug/commands.lua
@@ -449,7 +449,8 @@ RegisterAlyxLibCommand("sphere", function (_, x, y, z, r)
z = tonumber(z) or 0
r = tonumber(r) or 16
- DebugDrawSphere(Vector(x, y, z), Vector(255, 255, 255), 255, r, false, 10)
+ DebugDrawSphere(Vector(x, y, z), Vector(255, 255, 255), 255, r, false, 15)
+ DebugDrawSphere(Vector(x, y, z), Vector(255, 255, 255), 255, r, true, 10)
end, "Draws a debug sphere at 3D position with a radius", 0)
From bd1ae1c160e7cfe7343336db571ddf283af8c390 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Wed, 8 Oct 2025 13:04:12 +1300
Subject: [PATCH 077/146] add qangle and table to SimpleVector
---
scripts/vscripts/alyxlib/debug/common.lua | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/scripts/vscripts/alyxlib/debug/common.lua b/scripts/vscripts/alyxlib/debug/common.lua
index b8464c7..c842440 100644
--- a/scripts/vscripts/alyxlib/debug/common.lua
+++ b/scripts/vscripts/alyxlib/debug/common.lua
@@ -759,7 +759,7 @@ end
---
---Returns a simplified vector string with decimal places truncated.
---
----@param vector Vector
+---@param vector Vector|QAngle|table
---@return string
function Debug.SimpleVector(vector)
return "[" .. math.trunc(vector.x, 3) .. ", " .. math.trunc(vector.y, 3) .. ", " .. math.trunc(vector.z, 3) .. "]"
From 356cf4898d54da9b8cea0a407b3df164dd651194 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Wed, 22 Oct 2025 10:20:30 +1300
Subject: [PATCH 078/146] update weapon switch delay
---
scripts/vscripts/alyxlib/player/events.lua | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/scripts/vscripts/alyxlib/player/events.lua b/scripts/vscripts/alyxlib/player/events.lua
index b554853..1258d40 100644
--- a/scripts/vscripts/alyxlib/player/events.lua
+++ b/scripts/vscripts/alyxlib/player/events.lua
@@ -670,7 +670,7 @@ local function listenEventWeaponSwitch(data)
newdata.item_class = data.item
newdata.hand = handHandle
eventCallback(data.game_event_name, newdata)
- end, 0.0)
+ end, 0.01) -- smallest delay for hand_use_controller
end
end
ListenToGameEvent("weapon_switch", listenEventWeaponSwitch, nil)
From 8828f46b9b2c2ac9b58ed9546c2c71e135c93a6d Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Wed, 22 Oct 2025 10:21:21 +1300
Subject: [PATCH 079/146] fix incorrect equip detection
---
scripts/vscripts/alyxlib/player/data.lua | 72 +++++++++++++++---------
1 file changed, 46 insertions(+), 26 deletions(-)
diff --git a/scripts/vscripts/alyxlib/player/data.lua b/scripts/vscripts/alyxlib/player/data.lua
index f282b73..fc95697 100644
--- a/scripts/vscripts/alyxlib/player/data.lua
+++ b/scripts/vscripts/alyxlib/player/data.lua
@@ -56,29 +56,34 @@ local currentlyEquipped = nil
local previousHandle = nil
local currentHandle = nil
+-- These might need to be moved to CPropVRHand.CurrentlyEquipped
+-- if general use is desired, but offhand equip is rare
+---@type {[0]:string, [1]:string}
+local handEquipClass = {}
+
local syncPaused = false
---Standard local weapon offsets from hand when equipped
local weaponOffsets = {
[0] = { -- left
- hand_use_controller = Vector(0.027, 0.012, -0.124),
- hlvr_weapon_energygun = Vector(-0.025, 0.048, 2.014),
- hlvr_weapon_shotgun = Vector(0.402, 0.103, 1.652),
- hlvr_weapon_rapidfire = Vector(0.392, 0.102, 1.658),
- hlvr_multitool = Vector(3.079, 0.016, 1.209),
+ hand_use_controller = Vector(-4.8, 0.576, -0.262),
+ hlvr_weapon_energygun = Vector(-3.359, 0.667, 1.314),
+ hlvr_weapon_shotgun = Vector(-3.297, 0.641, 0.753),
+ hlvr_weapon_rapidfire = Vector(-3.3, 0.643, 0.767),
+ hlvr_multitool = Vector(-1.695, 0.099, -1.369),
-- unsure if offsets will be different for custom models,
-- using hlvr_weapon_energygun offset
- hlvr_weapon_generic_pistol = Vector(-0.025, 0.048, 2.014),
+ hlvr_weapon_generic_pistol = Vector(-3.359, 0.667, 1.314),
},
[1] = { -- right
- hand_use_controller = Vector(0.026, -0.009, -0.125),
- hlvr_weapon_energygun = Vector(-0.025, -0.042, 2.013),
- hlvr_weapon_shotgun = Vector(0.402, -0.097, 1.651),
- hlvr_weapon_rapidfire = Vector(0.392, -0.096, 1.657),
- hlvr_multitool = Vector(3.079, -0.006, 1.208),
+ hand_use_controller = Vector(-4.798, -0.674, -0.3),
+ hlvr_weapon_energygun = Vector(-3.399, -0.769, 1.244),
+ hlvr_weapon_shotgun = Vector(-3.289, -0.731, 0.711),
+ hlvr_weapon_rapidfire = Vector(-3.293, -0.731, 0.722),
+ hlvr_multitool = Vector(-1.692, -0.299, -1.442),
-- unsure if offsets will be different for custom models,
-- using hlvr_weapon_energygun offset
- hlvr_weapon_generic_pistol = Vector(-0.025, -0.042, 2.013),
+ hlvr_weapon_generic_pistol = Vector(-3.399, -0.769, 1.244),
},
}
@@ -137,13 +142,21 @@ local function SyncEquippedWeaponState(classname, handle)
for h = 0, 1 do
local hand = h == 0 and Player.LeftHand or Player.RightHand
local offset = weaponOffsets[h][classname] or Vector()
- for _, wpn in ipairs(Entities:FindAllByClassname(classname)) do
- if wpn ~= hand.ItemHeld then
- local dist = VectorDistance(offset, hand:TransformPointWorldToEntity(wpn:GetOrigin()))
- if dist < bestDistance then
- bestDistance = dist
- bestWeapon = wpn
- handHandle = hand
+ print("Looking for", classname, "in hand", hand:GetName(), "currentlyEquipped", handEquipClass[hand:GetHandID()])
+ -- If switching to hand_use_controller, it must not be the current equipped class
+ if classname ~= "hand_use_controller" or handEquipClass[hand:GetHandID()] ~= classname then
+ for _, wpn in ipairs(Entities:FindAllByClassname(classname)) do
+ -- Weapon must not be held
+ if wpn ~= hand.ItemHeld then
+ local dist = VectorDistance(offset, hand:TransformPointWorldToEntity(wpn:GetOrigin()))
+ -- debugoverlay:Sphere(wpn:GetOrigin(), 1, 0, 255, 0, 255, false, 5)
+ -- debugoverlay:Text(wpn:GetOrigin()+Vector(0,0,1), 0, tostring(dist), 0, 0, 255, 0, 255, 5)
+ -- print(dist, ":", hand:GetName(), entstr(wpn), wpn:GetModelName(), "Owner:", entstr(wpn:GetOwner()))
+ if dist < bestDistance then
+ bestDistance = dist
+ bestWeapon = wpn
+ handHandle = hand
+ end
end
end
end
@@ -153,16 +166,17 @@ local function SyncEquippedWeaponState(classname, handle)
weaponHandle = handle
- -- if weaponHandle then
- -- print("Setting new weapon for hand", entstr(handHandle), classname, entstr(weaponHandle))
- -- debugoverlay:Sphere(weaponHandle:GetOrigin(), 2, 255, 255, 255, 255, true, 100)
- -- debugoverlay:Text(weaponHandle:GetOrigin(), 0, entstr(weaponHandle).." : "..entstr(handHandle), 0, 255, 255, 255, 255, 100)
- -- end
-
if not IsValidEntity(weaponHandle) then
warn("Could not find weapon entity for equipped weapon: " .. classname)
+ return weaponHandle, handHandle
end
+ -- if weaponHandle then
+ -- print("Setting new weapon for hand", entstr(handHandle), classname, entstr(weaponHandle), "Owner:", entstr(weaponHandle:GetOwner()))
+ -- debugoverlay:Sphere(weaponHandle:GetOrigin(), 2, 255, 255, 255, 255, true, 5)
+ -- debugoverlay:Text(weaponHandle:GetOrigin(), 0, entstr(weaponHandle).." : "..entstr(handHandle), 0, 255, 255, 255, 255, 5)
+ -- end
+
currentHandle = weaponHandle
if handHandle == Player.PrimaryHand then
@@ -197,7 +211,13 @@ local function SyncEquippedWeaponState(classname, handle)
-- This event can fire before the player has a hand
if handHandle then
- handHandle.ItemHeld = weaponHandle
+ if classname == "hand_use_controller" then
+ handHandle.ItemHeld = nil
+ else
+ handHandle.ItemHeld = weaponHandle
+ end
+
+ handEquipClass[handHandle:GetHandID()] = classname
end
savePlayerData()
From c1fa12a8018f2518c385e0b714f90771c4d754df Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Wed, 22 Oct 2025 10:21:36 +1300
Subject: [PATCH 080/146] fix spell error
---
scripts/vscripts/alyxlib/player/core.lua | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/scripts/vscripts/alyxlib/player/core.lua b/scripts/vscripts/alyxlib/player/core.lua
index 02a018a..5346be3 100644
--- a/scripts/vscripts/alyxlib/player/core.lua
+++ b/scripts/vscripts/alyxlib/player/core.lua
@@ -669,7 +669,7 @@ function CBasePlayer:GetRapidfireUpgrades(weapon)
if child:GetClassname() == "reflex_sights" then
table.insert(upgrades, "rapidfire_upgrade_reflex_sight")
elseif child:GetName() == "rapidfire_laser_sight" then
- table.insert(upgrades, "rapidfire_upgrade_laser_right")
+ table.insert(upgrades, "rapidfire_upgrade_laser_sight")
elseif child:GetName() == "rapidfire_extended_magazine" then
table.insert(upgrades, "rapidfire_upgrade_extended_magazine")
end
From 9519fe4632df5050941a7ed3b9ee78468a42c206 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Wed, 22 Oct 2025 10:22:11 +1300
Subject: [PATCH 081/146] change TableFindIndex to TableFindKey
---
scripts/vscripts/alyxlib/debug/debug_menu.lua | 2 +-
scripts/vscripts/alyxlib/globals.lua | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index dd4dbc6..39d4f9b 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -867,7 +867,7 @@ function DebugMenu:SendCategoryToPanel(category)
if default ~= nil then
-- find the index of the default value
- local index = TableFindIndex(item.values, function(v) return tostring(v.value) == tostring(default) end)
+ local index = TableFindKey(item.values, function(v) return tostring(v.value) == tostring(default) end)
if index > 0 then
default = index
else
diff --git a/scripts/vscripts/alyxlib/globals.lua b/scripts/vscripts/alyxlib/globals.lua
index 916b87f..7f20c4f 100644
--- a/scripts/vscripts/alyxlib/globals.lua
+++ b/scripts/vscripts/alyxlib/globals.lua
@@ -610,14 +610,14 @@ end
---
---@param list table
---@param predicate fun(value:any):boolean
----@return integer
-function TableFindIndex(list, predicate)
- for i, v in ipairs(list) do
+---@return any
+function TableFindKey(list, predicate)
+ for i, v in pairs(list) do
if predicate(v) then
return i
end
end
- return 0
+ return nil
end
---
From 96fc2d6a1edffc47ba02df13a3f25ccbbd653f83 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Wed, 22 Oct 2025 10:23:14 +1300
Subject: [PATCH 082/146] remove default think
---
scripts/vscripts/alyxlib/class.lua | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/scripts/vscripts/alyxlib/class.lua b/scripts/vscripts/alyxlib/class.lua
index 57c022a..6cec76e 100644
--- a/scripts/vscripts/alyxlib/class.lua
+++ b/scripts/vscripts/alyxlib/class.lua
@@ -438,6 +438,7 @@ end
---@field OnBreak fun(self: EntityClass, inflictor: EntityHandle) # Called when a breakable entity is broken.
---@field OnTakeDamage fun(self: EntityClass, damageTable: OnTakeDamageTable) # Called when entity takes damage.
---@field Precache fun(self: EntityClass, context: CScriptPrecacheContext) # Called before Spawn for precaching.
+---@field Think function # Entity think function.
EntityClass = entity("EntityClass")
---Assign a new value to entity's field `name`.
@@ -469,16 +470,16 @@ function EntityClass:Save(name, value)
end
end
----Main entity think function which auto resumes on game load.
----@luadoc-ignore
-function EntityClass:Think()
- Warning("Trying to think on entity class with no think defined ["..self.__name.."]\n")
-end
+-- ---Main entity think function which auto resumes on game load.
+-- ---@luadoc-ignore
+-- function EntityClass:Think()
+-- Warning("Trying to think on entity class with no think defined ["..self.__name.."]\n")
+-- end
---Resume the entity think function.
---@luadoc-ignore
function EntityClass:ResumeThink()
- if not self:IsNull() then
+ if not self:IsNull() and type(self.Think) == "function" then
self:SetContextThink("__EntityThink", function()
-- Handle dead entity and user PauseThink used without returning nil
if not IsValidEntity(self) or not self.IsThinking then return nil end
From 78fbdad64c6ef33879c44d703dd699c62c813c68 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Wed, 22 Oct 2025 10:23:26 +1300
Subject: [PATCH 083/146] check for entity in isinstance
---
scripts/vscripts/alyxlib/class.lua | 2 ++
1 file changed, 2 insertions(+)
diff --git a/scripts/vscripts/alyxlib/class.lua b/scripts/vscripts/alyxlib/class.lua
index 6cec76e..1bb7413 100644
--- a/scripts/vscripts/alyxlib/class.lua
+++ b/scripts/vscripts/alyxlib/class.lua
@@ -591,6 +591,8 @@ end
---@return boolean # True if `ent` inherits `class`, false otherwise.
---@diagnostic disable-next-line:lowercase-global
function isinstance(ent, class)
+ if not IsEntity(ent, true) then return false end
+
local inherits = rawget(ent, "__inherits")
if inherits then
for _, inherit in ipairs(inherits) do
From 94b17e90ed431d1d5eb3b8241e75b41ecb5aa0ba Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Wed, 22 Oct 2025 10:24:05 +1300
Subject: [PATCH 084/146] add unpack cap to 99 (Lua limit)
---
scripts/vscripts/alyxlib/debug/common.lua | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/scripts/vscripts/alyxlib/debug/common.lua b/scripts/vscripts/alyxlib/debug/common.lua
index c842440..eebc9ca 100644
--- a/scripts/vscripts/alyxlib/debug/common.lua
+++ b/scripts/vscripts/alyxlib/debug/common.lua
@@ -187,7 +187,7 @@ function Debug.PrintEntityList(list, properties)
lenHandle = lenHandle + 1
local formatStr = "%-"..lenIndex.."s %-"..lenHandle.."s"
for propertyIndex, propertyTable in ipairs(propertyMetaData) do
- formatStr = formatStr .. " | %-"..propertyTable.max.."s"
+ formatStr = formatStr .. " | %-"..math.min(propertyTable.max, 99).."s"
end
formatStr = formatStr .. " | %-"..lenParent.."s"
@@ -591,7 +591,7 @@ end
--- val2 = RandomFloat(0, 1),
--- val3 = RandomFloat(0, 1)
--- })
---- ->
+---
--- 1^ []
--- | [] []
--- | [] [] []
From 34282fe79af445b090b5210501c68c71650e24a8 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Wed, 22 Oct 2025 10:24:24 +1300
Subject: [PATCH 085/146] change dynamic script to string name
---
.vscode/alyxlib.code-snippets | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.vscode/alyxlib.code-snippets b/.vscode/alyxlib.code-snippets
index fc449d3..f130e85 100644
--- a/.vscode/alyxlib.code-snippets
+++ b/.vscode/alyxlib.code-snippets
@@ -10,7 +10,7 @@
"if thisEntity then",
"\t-- Inherit this script if attached to entity",
"\t-- Will also load the script at the same time if needed",
- "\tinherit(GetScriptFile())",
+ "\tinherit(\"${1:EntityName}\")",
"\treturn",
"end",
"",
From 2dbaf4f317aba0b1575b58cfb9a5d25f0ec5ea53 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Fri, 24 Oct 2025 14:54:31 +1300
Subject: [PATCH 086/146] update deployment manifest
---
deployment_manifest.json | 28 ++++++++++++++++++++++++++++
1 file changed, 28 insertions(+)
diff --git a/deployment_manifest.json b/deployment_manifest.json
index 0e173ca..d273137 100644
--- a/deployment_manifest.json
+++ b/deployment_manifest.json
@@ -113,6 +113,34 @@
}
],
+ "upload_removal": [
+ {
+ "type": "delete",
+ "description": "Delete compiled panorama_lua.js",
+ "source": "{AddonGame}/panorama/scripts/custom_game/panorama_lua.vjs_c"
+ },
+ {
+ "type": "delete",
+ "description": "Delete compiled panoramadoc.js",
+ "source": "{AddonGame}/panorama/scripts/custom_game/panoramadoc.vjs_c"
+ },
+ {
+ "type": "delete",
+ "description": "Delete compiled alyxlib_debug_menu.xml",
+ "source": "{AddonGame}/panorama/layout/custom_game/alyxlib_debug_menu.vxml_c"
+ },
+ {
+ "type": "delete",
+ "description": "Delete compiled alyxlib_debug_menu.js",
+ "source": "{AddonGame}/panorama/scripts/custom_game/alyxlib_debug_menu.vjs_c"
+ },
+ {
+ "type": "delete",
+ "description": "Delete compiled alyxlib_debug_menu.css",
+ "source": "{AddonGame}/panorama/styles/custom_game/alyxlib_debug_menu.vcss_c"
+ }
+ ],
+
"sounds": [
{
"type": "copy",
From b22469fae6e5feacbcca895994496912ddfeac68 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Fri, 24 Oct 2025 14:55:16 +1300
Subject: [PATCH 087/146] rename params to event
---
.vscode/alyxlib.code-snippets | 8 ++++----
scripts/vscripts/alyxlib/class.lua | 4 ++--
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/.vscode/alyxlib.code-snippets b/.vscode/alyxlib.code-snippets
index f130e85..30c9b08 100644
--- a/.vscode/alyxlib.code-snippets
+++ b/.vscode/alyxlib.code-snippets
@@ -45,8 +45,8 @@
"scope": "lua",
"prefix": ["Game Event"],
"body": [
- "---@param params GAME_EVENT_$2",
- "base:GameEvent(${1:\"event\"}, function(self, params)",
+ "---@param event GAME_EVENT_$2",
+ "base:GameEvent(${1:\"event\"}, function(self, event)",
"\t---@cast self ${3:ClassName}",
"",
"\t$0",
@@ -59,8 +59,8 @@
"scope": "lua",
"prefix": ["Player Event"],
"body": [
- "---@param params PLAYER_EVENT_$2",
- "base:PlayerEvent(${1:\"event\"}, function(self, params)",
+ "---@param event PLAYER_EVENT_$2",
+ "base:PlayerEvent(${1:\"event\"}, function(self, event)",
"\t---@cast self ${3:ClassName}",
"",
"\t$0",
diff --git a/scripts/vscripts/alyxlib/class.lua b/scripts/vscripts/alyxlib/class.lua
index 1bb7413..1c56703 100644
--- a/scripts/vscripts/alyxlib/class.lua
+++ b/scripts/vscripts/alyxlib/class.lua
@@ -508,14 +508,14 @@ end
---Define a function for listening to a game event.
---@param gameEvent GameEventsAll
----@param func fun(self: EntityClass, params):any
+---@param func fun(self: EntityClass, event):any
function EntityClass:GameEvent(gameEvent, func)
self.__game_events[gameEvent] = func
end
---Define a function for listening to a player event.
---@param playerEvent PLAYER_EVENTS_ALL
----@param func fun(self, params):any
+---@param func fun(self, event):any
function EntityClass:PlayerEvent(playerEvent, func)
self.__player_events[playerEvent] = func
end
From 3b748d2f00707f204512df5bbaec1bcf36a1f755 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Fri, 24 Oct 2025 14:55:27 +1300
Subject: [PATCH 088/146] fix wrong type
---
.vscode/vlua_snippets.code-snippets | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.vscode/vlua_snippets.code-snippets b/.vscode/vlua_snippets.code-snippets
index 602f8b8..0f58b73 100644
--- a/.vscode/vlua_snippets.code-snippets
+++ b/.vscode/vlua_snippets.code-snippets
@@ -48,7 +48,7 @@
"Hook: OnTakeDamage":{
"scope": "lua",
- "prefix": ["Hook", "OnTakeDamage", "Snippet"],
+ "prefix": ["Hook", "OnTakeDamageTable", "Snippet"],
"body": [
"---@param damageTable TypeDamageTable",
"function OnTakeDamage(damageTable)",
From f0c2ac219067df7a3dc209b48efa7ca45f4dd2b0 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Fri, 24 Oct 2025 14:55:45 +1300
Subject: [PATCH 089/146] Warning to warn
---
scripts/vscripts/alyxlib/class.lua | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/scripts/vscripts/alyxlib/class.lua b/scripts/vscripts/alyxlib/class.lua
index 1c56703..0904f70 100644
--- a/scripts/vscripts/alyxlib/class.lua
+++ b/scripts/vscripts/alyxlib/class.lua
@@ -110,7 +110,7 @@ local function _inherit(base, self, fenv)
break
end
end
- Warning("Trying to inherit an already inherited class ( "..class_name.." -> ["..tostring(self)..","..self:GetClassname()..","..self:GetName().."] )\n")
+ warn("Trying to inherit an already inherited class ( "..class_name.." -> ["..tostring(self)..","..self:GetClassname()..","..self:GetName().."] )")
return
end
From c2e665260cd7e2f2eba2f36f9e9c75d79343676b Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Fri, 24 Oct 2025 14:56:02 +1300
Subject: [PATCH 090/146] add class binding
---
scripts/vscripts/alyxlib/class.lua | 37 ++++++++++++++++++++++++++++++
1 file changed, 37 insertions(+)
diff --git a/scripts/vscripts/alyxlib/class.lua b/scripts/vscripts/alyxlib/class.lua
index 0904f70..c33245e 100644
--- a/scripts/vscripts/alyxlib/class.lua
+++ b/scripts/vscripts/alyxlib/class.lua
@@ -624,4 +624,41 @@ function IsClassEntity(ent)
return type(name) == "string" and EntityClassNameMap[name] ~= nil
end
+---
+---Binds a class to an already spawned entity so it remains attached between game loads.
+---
+---@param entity EntityHandle # Entity to bind to.
+---@param script string|table # Script, class, or class name to bind.
+---@param activateType any
+local function doClassBind(entity, script, activateType)
+ local scope = entity:GetOrCreatePrivateScriptScope()
+ print("inheriting", scope, script)
+ inherit(script, scope)
+ if activateType and type(scope.Activate) == "function" then
+ scope.Activate(activateType)
+ end
+end
+
+---
+---Binds a class to an already spawned entity so it remains attached between game loads.
+---
+---@param entity EntityHandle # Entity to bind to.
+---@param script string # Script, class, or class name to bind.
+function BindClass(entity, script)
+ doClassBind(entity, script, 0)
+ entity:SetContext("BoundEntityClassScript", script, 0)
+end
+
+---@param event PlayerEventPlayerActivate
+ListenToPlayerEvent("player_activate", function(event)
+ local ent = Entities:First()
+ while ent do
+ local script = ent:GetContext("BoundEntityClassScript")
+ if type(script) == "string" then
+ doClassBind(ent, script)
+ end
+ ent = Entities:Next(ent)
+ end
+end)
+
return version
From 5f6f3f5d64ee2cc5e883c161764cf2b9a8d562c8 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Fri, 24 Oct 2025 14:56:17 +1300
Subject: [PATCH 091/146] update precache
---
scripts/vscripts/alyxlib/precache.lua | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/scripts/vscripts/alyxlib/precache.lua b/scripts/vscripts/alyxlib/precache.lua
index f9d8839..b575dde 100644
--- a/scripts/vscripts/alyxlib/precache.lua
+++ b/scripts/vscripts/alyxlib/precache.lua
@@ -61,7 +61,6 @@ _G.AlyxLibGlobalPrecacheList = {}
---@param path string # The asset path to precache (or the classname if type is entity).
---@param spawnkeys table? # The spawnkeys table if type is entity.
function GlobalPrecache(type, path, spawnkeys)
- print("Adding global precache:", type, path)
table.insert(AlyxLibGlobalPrecacheList, {
type = type,
path = path,
@@ -89,7 +88,7 @@ end
---
---If you are precaching *after* the player has spawned, call this function after preceding [GlobalPrecache](lua://GlobalPrecache) calls.
---
----@param callback function # The function to call when the precaching is complete.
+---@param callback? function # The function to call when the precaching is complete.
function GlobalPrecacheFlush(callback)
precacheAsync(function()
AlyxLibGlobalPrecacheList = {}
From 4539c67ec8ab54041586b78a659901a2477b162c Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Fri, 24 Oct 2025 14:56:56 +1300
Subject: [PATCH 092/146] disable print
---
scripts/vscripts/alyxlib/player/data.lua | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/scripts/vscripts/alyxlib/player/data.lua b/scripts/vscripts/alyxlib/player/data.lua
index fc95697..7213995 100644
--- a/scripts/vscripts/alyxlib/player/data.lua
+++ b/scripts/vscripts/alyxlib/player/data.lua
@@ -142,7 +142,9 @@ local function SyncEquippedWeaponState(classname, handle)
for h = 0, 1 do
local hand = h == 0 and Player.LeftHand or Player.RightHand
local offset = weaponOffsets[h][classname] or Vector()
- print("Looking for", classname, "in hand", hand:GetName(), "currentlyEquipped", handEquipClass[hand:GetHandID()])
+
+ -- print("Looking for", classname, "in hand", hand:GetName(), "currentlyEquipped", handEquipClass[hand:GetHandID()])
+
-- If switching to hand_use_controller, it must not be the current equipped class
if classname ~= "hand_use_controller" or handEquipClass[hand:GetHandID()] ~= classname then
for _, wpn in ipairs(Entities:FindAllByClassname(classname)) do
From 3ff3cc91ad5104c193ab856f8c5a51e375c10fe0 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Fri, 31 Oct 2025 21:10:50 +1300
Subject: [PATCH 093/146] use single underscore for unsaved class members
this also fixes unsaved members not cloning into entity on instantiation
---
scripts/vscripts/alyxlib/class.lua | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/scripts/vscripts/alyxlib/class.lua b/scripts/vscripts/alyxlib/class.lua
index c33245e..de03fad 100644
--- a/scripts/vscripts/alyxlib/class.lua
+++ b/scripts/vscripts/alyxlib/class.lua
@@ -133,7 +133,7 @@ local function _inherit(base, self, fenv)
}
-- Used to automatically save values
meta.__newindex = function(table, key, value)
- if not key:startswith("__") and type(value) ~= "function" then
+ if not key:startswith("_") and type(value) ~= "function" then
meta.__values[key] = value
self:Save(key, value)
else
@@ -464,7 +464,7 @@ function EntityClass:Save(name, value)
Storage.Save(self, name, value~=nil and value or self[name])
end
for key, val in pairs(self) do
- if not key:startswith("__") and type(val) ~= "function" then
+ if not key:startswith("_") and type(val) ~= "function" then
Storage.Save(self, key, val)
end
end
From d27c1840738a81562d3668cef8535670012b6d7c Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Fri, 31 Oct 2025 21:11:20 +1300
Subject: [PATCH 094/146] fix all members saving when name is specified
---
scripts/vscripts/alyxlib/class.lua | 2 ++
1 file changed, 2 insertions(+)
diff --git a/scripts/vscripts/alyxlib/class.lua b/scripts/vscripts/alyxlib/class.lua
index de03fad..9f07a32 100644
--- a/scripts/vscripts/alyxlib/class.lua
+++ b/scripts/vscripts/alyxlib/class.lua
@@ -462,7 +462,9 @@ function EntityClass:Save(name, value)
if name then
Storage.Save(self, name, value~=nil and value or self[name])
+ return
end
+
for key, val in pairs(self) do
if not key:startswith("_") and type(val) ~= "function" then
Storage.Save(self, key, val)
From fcf148bbe3ac7a244e48570856eb46a488164352 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Mon, 3 Nov 2025 19:11:47 +1300
Subject: [PATCH 095/146] always return result
---
scripts/vscripts/alyxlib/globals.lua | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/scripts/vscripts/alyxlib/globals.lua b/scripts/vscripts/alyxlib/globals.lua
index 7f20c4f..9195050 100644
--- a/scripts/vscripts/alyxlib/globals.lua
+++ b/scripts/vscripts/alyxlib/globals.lua
@@ -247,9 +247,9 @@ function ifrequire(modname, callback)
if callback then
callback(result)
- return result
end
- return nil
+
+ return result
end
---
From 3bca81240c1988e11df5b1924693d48651cf9ff8 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Mon, 3 Nov 2025 19:18:12 +1300
Subject: [PATCH 096/146] return think name
---
scripts/vscripts/alyxlib/extensions/entity.lua | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/scripts/vscripts/alyxlib/extensions/entity.lua b/scripts/vscripts/alyxlib/extensions/entity.lua
index b5f998c..fff7b82 100644
--- a/scripts/vscripts/alyxlib/extensions/entity.lua
+++ b/scripts/vscripts/alyxlib/extensions/entity.lua
@@ -466,21 +466,25 @@ end
--- end
--- end)
---
----@param propertyFunction fun(handle: EntityHandle): any # Property function to track, e.g. GetRenderAlpha.
----@param onChangeFunction fun(prevValue: any, newValue: any) # Function to call when a change is detected.
----@param interval? number # Think interval, or smallest possible if nil.
----@param context? EntityHandle # Entity to run the thinker on, or this entity if nil.
+---@param propertyFunction fun(handle: EntityHandle): any # Property function to track
+---@param onChangeFunction fun(prevValue: any, newValue: any) # Function to call when a change is detected
+---@param interval? number # Think interval (default: 0)
+---@param context? EntityHandle # Entity to run the thinker on, or the calling entity if `nil`
+---@return string # The name of the think for stopping later
function CBaseEntity:TrackProperty(propertyFunction, onChangeFunction, interval, context)
interval = interval or 0
context = context or self
local value = propertyFunction(self)
- context:SetContextThink(tostring(propertyFunction), function()
+ local name = tostring(propertyFunction)
+ context:SetContextThink(name, function()
local newValue = propertyFunction(self)
if newValue ~= value then
onChangeFunction(value, newValue)
end
return interval
end, interval)
+
+ return name
end
---
From 3ab8aa7c1c90ab771d565990c983ce6e29764d56 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Mon, 3 Nov 2025 19:20:01 +1300
Subject: [PATCH 097/146] escape splits
---
scripts/vscripts/alyxlib/extensions/string.lua | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/scripts/vscripts/alyxlib/extensions/string.lua b/scripts/vscripts/alyxlib/extensions/string.lua
index 8f172d9..04dd306 100644
--- a/scripts/vscripts/alyxlib/extensions/string.lua
+++ b/scripts/vscripts/alyxlib/extensions/string.lua
@@ -11,11 +11,13 @@
local version = "v1.3.1"
----
----Gets if a string starts with a substring.
----@param s string
----@param substr string
----@return boolean
+---Escapes special characters in a string.
+---@param s string # The string to escape
+---@return string # The escaped string
+local function escape(s)
+ return (s:gsub("([^%w])", "%%%1"))
+end
+
function string.startswith(s, substr)
return s:sub(1, #substr) == substr
end
@@ -55,6 +57,8 @@ end
function string.split(s, sep)
if sep == nil then
sep = '%s'
+ else
+ sep = escape(sep)
end
return string.splitraw(s, '([^'..sep..']+)')
end
From 2a7951902678a475c244557b1c586e2cf43b4fe4 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Mon, 3 Nov 2025 19:22:53 +1300
Subject: [PATCH 098/146] rename string trims to slices
original functions weren't behaving like traditional trimming
new trim functions have been added to replace them
---
.../vscripts/alyxlib/extensions/string.lua | 65 +++++++++++++++----
1 file changed, 53 insertions(+), 12 deletions(-)
diff --git a/scripts/vscripts/alyxlib/extensions/string.lua b/scripts/vscripts/alyxlib/extensions/string.lua
index 04dd306..296ca83 100644
--- a/scripts/vscripts/alyxlib/extensions/string.lua
+++ b/scripts/vscripts/alyxlib/extensions/string.lua
@@ -80,27 +80,68 @@ function string.truncate(s, len, replacement)
end
---
----Trims characters from the left side of the string up to the last occurrence of a specified character.
+---Slices the string from the left, returning everything after the last occurrence of a specified character.
---
----@param s string
----@param char string # The character to trim the string at the last occurrence.
----@return string # The trimmed string.
-function string.trimleft(s, char)
- local index = s:match(".*" .. char .. "()")
+---@param s string # The string to slice
+---@param char string # The character to trim the string at the last occurrence
+---@return string # The trimmed string
+function string.sliceleft(s, char)
+ local index = s:match(".*" .. escape(char) .. "()")
return index and s:sub(index) or s
end
+
---
----Trims characters from the right side of the string up to the last occurrence of a specified character.
+---Slices the string from the right, returning everything before the first occurrence of a specified character.
---
----@param s string
----@param char string # The character to trim the string at the last occurrence.
----@return string # The trimmed string.
-function string.trimright(s, char)
- local index = s:find(char)
+---@param s string # The string to slice
+---@param char string # The character to trim the string at the last occurrence
+---@return string # The trimmed string
+function string.sliceright(s, char)
+ local index = s:find(escape(char))
return index and s:sub(1, index - 1) or s
end
+---
+---Trims characters from the left side of a string.
+---
+---@param s string # String to trim
+---@param chars? string # Characters to trim (defaults to whitespace)
+---@return string # The trimmed string
+function string.trimleft(s, chars)
+ if chars == nil then
+ chars = "%s"
+ else
+ chars = escape(chars)
+ end
+ return (s:gsub("^[" .. chars .. "]+", ""))
+end
+
+---
+---Trims characters from the right side of a string.
+---
+---@param s string # String to trim
+---@param chars? string # Characters to trim (defaults to whitespace)
+---@return string # The trimmed string
+function string.trimright(s, chars)
+ if chars == nil then
+ chars = "%s"
+ else
+ chars = escape(chars)
+ end
+ return (s:gsub("[" .. chars .. "]+$", ""))
+end
+
+---
+---Trims characters from both sides of a string.
+---
+---@param s string # String to trim
+---@param chars? string # Characters to trim (defaults to whitespace)
+---@return string # The trimmed string
+function string.trim(s, chars)
+ return string.trimleft(string.trimright(s, chars), chars)
+end
+
---
---Capitalizes letters in the input string.
---
From 025554160d22122213da74e100ce0eb11b65e130 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Mon, 3 Nov 2025 19:25:04 +1300
Subject: [PATCH 099/146] allow multiple post-initializers
SetPostInitializer has been deprecated and provides the same behavior as
AddPostInitializer
---
.../vscripts/alyxlib/helpers/easyconvars.lua | 26 +++++++++++++++----
1 file changed, 21 insertions(+), 5 deletions(-)
diff --git a/scripts/vscripts/alyxlib/helpers/easyconvars.lua b/scripts/vscripts/alyxlib/helpers/easyconvars.lua
index a03269f..dbfbbe4 100644
--- a/scripts/vscripts/alyxlib/helpers/easyconvars.lua
+++ b/scripts/vscripts/alyxlib/helpers/easyconvars.lua
@@ -519,12 +519,28 @@ function EasyConvars:SetIfUnchanged(name, value)
end
end
+---@type function[]
+local postInitializers = {}
+
+---
+---Adds a function to be called after all convars have been initialized.
+---
+---Useful for setting convars that are dependent on other convars.
+---
+---@param func function # The function to call
+function EasyConvars:AddPostInitializer(func)
+ table.insert(postInitializers, func)
+end
+
+---
+---Adds a function to be called after all convars have been initialized.
---
----Sets the function to be called after all convars have been initialized.
+---**Deprecated: Use [AddPostInitializer](lua://EasyConvars.AddPostInitializer) instead**
---
----@param func function
+---@deprecated
+---@param func function # The function to call
function EasyConvars:SetPostInitializer(func)
- postInitializer = func
+ self:AddPostInitializer(func)
end
@@ -553,8 +569,8 @@ listener("player_activate", function (params)
end
EasyConvars._isLoading = false
- if postInitializer then
- postInitializer()
+ for _, func in pairs(postInitializers) do
+ func()
end
end)
From 9b6521eec21e31d2f4e7bdc66cc5c86521d807aa Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Mon, 3 Nov 2025 19:25:41 +1300
Subject: [PATCH 100/146] improve math.trunc
---
scripts/vscripts/alyxlib/math/common.lua | 18 ++++++++++++------
1 file changed, 12 insertions(+), 6 deletions(-)
diff --git a/scripts/vscripts/alyxlib/math/common.lua b/scripts/vscripts/alyxlib/math/common.lua
index 5360961..7f6a817 100644
--- a/scripts/vscripts/alyxlib/math/common.lua
+++ b/scripts/vscripts/alyxlib/math/common.lua
@@ -21,14 +21,20 @@ function math.sign(x)
end
---
----Truncates a number to the specified number of decimal places.
+---Truncates a number to a given number of decimal places.
---
----@param number number # The input number.The input number.
----@param places? integer # The number of decimal places to keep.
----@return number # The input number truncated to the specified decimal places.
+---@param number number # The number to truncate
+---@param places? integer # Decimal places to truncate to (default: 0)
+---@return number # The truncated number
function math.trunc(number, places)
- local shift = 10 ^ (places or 1)
- return math.floor(number * shift) / shift
+ places = places or 0
+ local factor = 10 ^ places
+ if places >= 0 then
+ return math.floor(number * factor) / factor
+ else
+ factor = 10 ^ (-places)
+ return math.floor(number / factor) * factor
+ end
end
---
From 9a9451b7cfa090d28e820960fb9f33ed6e7b946a Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Mon, 3 Nov 2025 19:26:54 +1300
Subject: [PATCH 101/146] add global move type function
---
scripts/vscripts/alyxlib/player/core.lua | 21 +++++++++++++++++++--
1 file changed, 19 insertions(+), 2 deletions(-)
diff --git a/scripts/vscripts/alyxlib/player/core.lua b/scripts/vscripts/alyxlib/player/core.lua
index 5346be3..5320744 100644
--- a/scripts/vscripts/alyxlib/player/core.lua
+++ b/scripts/vscripts/alyxlib/player/core.lua
@@ -285,19 +285,36 @@ PlayerMoveType = {
---
---Get VR movement type.
---
----@return PlayerMoveType
+---@return PlayerMoveType # The VR movement type
function CBasePlayer:GetMoveType()
return Convars:GetInt('hlvr_movetype_default') --[[@as PlayerMoveType]]
end
+---
+---Get VR movement type.
+---
+---@return PlayerMoveType # The VR movement type
+function GetPlayerMoveType()
+ return Convars:GetInt('hlvr_movetype_default') --[[@as PlayerMoveType]]
+end
+
---
---Sets the VR movement type.
---
----@param movetype PlayerMoveType
+---@param movetype PlayerMoveType # The VR movement type
function CBasePlayer:SetMoveType(movetype)
Convars:SetInt("vr_movetype_set", movetype)
end
+---
+---Sets the VR movement type.
+---
+---@param movetype PlayerMoveType # The VR movement type
+function SetPlayerMoveType(movetype)
+ Convars:SetInt("vr_movetype_set", movetype)
+end
+
+---
---Returns the entity the player is looking at directly.
---@param maxDistance? number # Max distance the trace can search.
---@return EntityHandle?
From 58b6977c178affe825ab10538efbf388a622ce51 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Mon, 3 Nov 2025 22:53:17 +1300
Subject: [PATCH 102/146] complete reformat of annotations
---
scripts/vscripts/alyxlib/class.lua | 221 +++++-------
scripts/vscripts/alyxlib/controls/gesture.lua | 112 +++---
scripts/vscripts/alyxlib/controls/haptics.lua | 11 +-
scripts/vscripts/alyxlib/controls/input.lua | 163 +++++----
scripts/vscripts/alyxlib/data/color.lua | 134 +++----
scripts/vscripts/alyxlib/data/inventory.lua | 89 ++---
scripts/vscripts/alyxlib/data/queue.lua | 104 +++---
scripts/vscripts/alyxlib/data/stack.lua | 83 +++--
scripts/vscripts/alyxlib/debug/commands.lua | 32 +-
scripts/vscripts/alyxlib/debug/common.lua | 100 +++---
scripts/vscripts/alyxlib/debug/controller.lua | 3 +-
scripts/vscripts/alyxlib/debug/debug_menu.lua | 67 ++--
scripts/vscripts/alyxlib/debug/novr.lua | 16 +-
scripts/vscripts/alyxlib/debug/profiling.lua | 20 +-
scripts/vscripts/alyxlib/debug/vr.lua | 2 +-
.../vscripts/alyxlib/extensions/entities.lua | 136 +++----
.../vscripts/alyxlib/extensions/entity.lua | 193 +++++-----
scripts/vscripts/alyxlib/extensions/npc.lua | 65 ++--
.../vscripts/alyxlib/extensions/qangle.lua | 2 +-
.../vscripts/alyxlib/extensions/string.lua | 51 +--
.../vscripts/alyxlib/extensions/template.lua | 2 +-
.../vscripts/alyxlib/extensions/vector.lua | 63 ++--
scripts/vscripts/alyxlib/globals.lua | 329 +++++++++--------
.../vscripts/alyxlib/helpers/animation.lua | 5 +-
.../vscripts/alyxlib/helpers/easyconvars.lua | 71 ++--
scripts/vscripts/alyxlib/math/common.lua | 34 +-
scripts/vscripts/alyxlib/math/obb.lua | 114 +++---
.../vscripts/alyxlib/math/weighted_random.lua | 36 +-
scripts/vscripts/alyxlib/panorama/core.lua | 22 +-
scripts/vscripts/alyxlib/player/core.lua | 275 ++++++++------
scripts/vscripts/alyxlib/player/data.lua | 32 +-
scripts/vscripts/alyxlib/player/events.lua | 72 ++--
scripts/vscripts/alyxlib/player/hands.lua | 69 ++--
.../alyxlib/player/wrist_attachments.lua | 66 ++--
scripts/vscripts/alyxlib/precache.lua | 35 +-
scripts/vscripts/alyxlib/storage.lua | 340 ++++++------------
scripts/vscripts/alyxlib/utils/common.lua | 110 +++---
scripts/vscripts/alyxlib/utils/enums.lua | 4 +
.../alyxlib/utils/extension_duplicate_fix.lua | 2 +-
39 files changed, 1700 insertions(+), 1585 deletions(-)
diff --git a/scripts/vscripts/alyxlib/class.lua b/scripts/vscripts/alyxlib/class.lua
index 9f07a32..23dd473 100644
--- a/scripts/vscripts/alyxlib/class.lua
+++ b/scripts/vscripts/alyxlib/class.lua
@@ -2,77 +2,11 @@
v2.3.1
https://github.com/FrostSource/alyxlib
- If not using `vscripts/alyxlib/core.lua`, load this file at game start using the following line:
-
- ```lua
- require "alyxlib.class"
- ```
-
- ======================================== Entity Classes ========================================
+ Provides object-oriented class and inheritance functionality.
- NOTE: Entity classes are still early in development and documentation is sparse.
+ If not using `alyxlib/init.lua`, load this file at game start using the following line:
- The new `entity` function is designed to ease the creation of entity classes and all the trouble
- that comes with complex entity scripts. Instead of defining functions and variables in the
- private script scope we define them in a shared table class which is inherited by all entities
- using this class.
-
- The simplest example of the entity function is:
-
- ```lua
- ---Define the entity class, which returns both the class (base) and entity handle (self).
- ---By "inheriting" EntityClass we get helpful code completion.
- ---@class EntityName : EntityClass
- local base, self = entity("EntityName")
- ---This stops code executing a second time after a game load.
- if self.Initiated then return end
-
- ---A variable is defined for the class.
- base.class_variable = 1
-
- ---A function is defined for the class. This specific function is called automatically.
- ---@param loaded boolean
- function base:OnReady(loaded)
- ---Reference a class variable, note that we use `self` instead of `base` here inside the function.
- self.class_variable = self.class_variable + 1
- end
- ```
-
- Entity scripts using this feature should *not* use `thisEntity` and instead use `self`.
- It's important to remember to define your class functions on the `base` class. Functions
- defined on `self` will not be inherited by other classes which inherit your class.
-
- Thinking is simplified, allowing you to define a base class think which can be paused/resumed
- and the state of which will be saved, meaning the think function will automatically resume
- after a game load:
-
- ```lua
- function base:OnReady(loaded)
- if not loaded then
- self:ResumeThink()
- end
- end
-
- function base:Think()
- if Time() > 50 then
- self:PauseThink()
- return
- end
- return 0
- end
- ```
-
- Like the think state, any fields defined in the `base`/`self` objects will be automatically loaded.
- Currently these values still need to be saved manually but this is done easily with the new save function:
-
- ```lua
- base.value = 1
-
- function base:ChangeValue()
- self.value = self.value + 1
- self:Save()
- end
- ```
+ require "alyxlib.class"
]]
local version = "v2.3.1"
@@ -291,12 +225,17 @@ local function _inherit(base, self, fenv)
end
end
----Inherit an existing entity class which was defined using `entity` function.
+---
+---Inherits an existing entity class which was defined using the [entity](lua://entity) function.
+---
+---If no entity is provided, the entity calling the code will inherit the class.
+---If no calling entity is found, an error will be thrown.
+---
---@generic T
----@param script `T` # The script to inherit.
----@param entity? EntityHandle # Optional entity which will inherit the script. If not used, the entity running the code will inherit.
----@return T # Base class, the newly created class.
----@return T # Self instance, the entity inheriting `base`.
+---@param script `T` # The class/script to inherit
+---@param entity? EntityHandle # Entity which will inherit the class
+---@return T # Inherited class
+---@return T # `self` instance of `entity`, the entity inheriting `script`
---@diagnostic disable-next-line:lowercase-global
function inherit(script, entity)
local fenv = entity or getfenv(2)
@@ -339,13 +278,13 @@ end
---The class is only created once so this can be called in entity attached scripts
---multiple times and all subsequent calls will return the already created class.
---
----@generic T, T2
+---@generic T, T2
---@param name? `T` # Internal class name
----@param ... string|table # Any inherit classes or scripts.
----@return any # Base class, the newly created class.
----@return T # Self instance, the entity inheriting `base`.
----@return table # Super class, the first inheritance of `base`.
----@return table # Private table
+---@param ... string|table # Any classes or scripts inherit
+---@return any # The newly created class
+---@return T # `self` instance of entity inheriting the class, if called in an attached script
+---@return table # The first inherited class, if any
+---@return table # Private members table of the class **(unused)**
---@diagnostic disable-next-line: lowercase-global
function entity(name, ...)
@@ -421,30 +360,34 @@ end
--#region EntityClass Definition
+---
---The top-level entity class that provides base functionality.
+---
---@class EntityClass : CBaseEntity,CEntityInstance,CBaseModelEntity,CBasePlayer,CHL2_Player,CBaseAnimating,CBaseFlex,CBaseCombatCharacter,CAI_BaseNPC,CBaseTrigger,CEnvEntityMaker,CInfoWorldLayer,CLogicRelay,CMarkupVolumeTagged,CEnvProjectedTexture,CPhysicsProp,CSceneEntity,CPointClientUIWorldPanel,CPointTemplate,CPointWorldText,CPropHMDAvatar,CPropVRHand
----@field __inherits table # Table of inherited classes.
----@field __name string # Name of the class.
----@field __outputs table # Map of output names to functions that will be connected on spawn.
----@field __game_events table # Map of game events to functions that will be listened to on spawn.
----@field __player_events table # Map of player events to functions that will be listened to on spawn.
----@field __rawget fun(self: EntityClass, key: string): any # Custom rawget function to get a value from meta.__values without checking inherits.
----@field Initiated boolean # If the class entity has been activated.
----@field IsThinking boolean # If the entity is currently thinking with `Think` function.
----@field OnActivate fun(self: EntityClass, activateType: ActivationType) # Called automatically on `Activate` if defined.
----@field OnReady fun(self: EntityClass, readyType: OnReadyType) # Called automatically after `Activate`, if defined, when EasyConvars and Player are initialized.
----@field OnSpawn fun(self: EntityClass, spawnkeys: CScriptKeyValues) # Called automatically on `Spawn` if defined.
----@field UpdateOnRemove fun(self: EntityClass) # Called before the entity is killed.
----@field OnBreak fun(self: EntityClass, inflictor: EntityHandle) # Called when a breakable entity is broken.
----@field OnTakeDamage fun(self: EntityClass, damageTable: OnTakeDamageTable) # Called when entity takes damage.
----@field Precache fun(self: EntityClass, context: CScriptPrecacheContext) # Called before Spawn for precaching.
+---@field __inherits table # Table of inherited classes
+---@field __name string # Name of the class
+---@field __outputs table # Map of output names to functions that will be connected on spawn
+---@field __game_events table # Map of game events to functions that will be listened to on spawn
+---@field __player_events table # Map of player events to functions that will be listened to on spawn
+---@field __rawget fun(self: EntityClass, key: string): any # Custom rawget function to get a value from meta.__values without checking inherits
+---@field Initiated boolean # If the class entity has been activated
+---@field IsThinking boolean # If the entity is currently thinking with `Think` function
+---@field OnActivate fun(self: EntityClass, activateType: ActivationType) # Called automatically on `Activate` if defined
+---@field OnReady fun(self: EntityClass, readyType: OnReadyType) # Called automatically after `Activate`, if defined, when EasyConvars and Player are initialized
+---@field OnSpawn fun(self: EntityClass, spawnkeys: CScriptKeyValues) # Called automatically on `Spawn` if defined
+---@field UpdateOnRemove fun(self: EntityClass) # Called before the entity is killed
+---@field OnBreak fun(self: EntityClass, inflictor: EntityHandle) # Called when a breakable entity is broken
+---@field OnTakeDamage fun(self: EntityClass, damageTable: OnTakeDamageTable) # Called when entity takes damage
+---@field Precache fun(self: EntityClass, context: CScriptPrecacheContext) # Called before Spawn for precaching
---@field Think function # Entity think function.
EntityClass = entity("EntityClass")
----Assign a new value to entity's field `name`.
+---
+---Assigns a new value to entity's field `name`.
---This also saves the field.
----@param name string
----@param value any
+---
+---@param name string # Name of the field
+---@param value any # Value to assign
---@deprecated # Values are automatically saved now.
function EntityClass:Set(name, value)
-- self[name] = value
@@ -453,9 +396,14 @@ function EntityClass:Set(name, value)
self:Save(name, value)
end
----Save a given entity field. Call with no arguments to save all data.
----@param name? string # Name of the field to save.
----@param value? any # Value to save. If not provided the value will be retrieved from the field with the same `name`.
+---
+---Manually saves a given entity field.
+---
+---If no `value` is provided, the value of the field with the same `name` will be saved.
+---If no `name` is provided, all fields will be saved.
+---
+---@param name? string # Name of the field to save
+---@param value? any # Value to save
---@luadoc-ignore
function EntityClass:Save(name, value)
if not IsValidEntity(self) then return end
@@ -478,7 +426,9 @@ end
-- Warning("Trying to think on entity class with no think defined ["..self.__name.."]\n")
-- end
----Resume the entity think function.
+---
+---Resumes the entity think function.
+---
---@luadoc-ignore
function EntityClass:ResumeThink()
if not self:IsNull() and type(self.Think) == "function" then
@@ -492,7 +442,9 @@ function EntityClass:ResumeThink()
end
end
----Pause the entity think function.
+---
+---Pauses the entity think function.
+---
---@luadoc-ignore
function EntityClass:PauseThink()
if not self:IsNull() then
@@ -501,21 +453,27 @@ function EntityClass:PauseThink()
end
end
----Define a function to redirected to `output` on spawn.
+---
+---Defines a function to redirected to IO `output` on spawn.
+---
---@param output string
---@param func fun(...):any
function EntityClass:Output(output, func)
self.__outputs[output] = func
end
----Define a function for listening to a game event.
+---
+---Defines a function for listening to a game event.
+---
---@param gameEvent GameEventsAll
---@param func fun(self: EntityClass, event):any
function EntityClass:GameEvent(gameEvent, func)
self.__game_events[gameEvent] = func
end
----Define a function for listening to a player event.
+---
+---Defines a function for listening to a player event.
+---
---@param playerEvent PLAYER_EVENTS_ALL
---@param func fun(self, event):any
function EntityClass:PlayerEvent(playerEvent, func)
@@ -525,7 +483,9 @@ end
--#endregion
+---
---Prints all classes that `ent` inherits.
+---
---@param ent EntityClass
---@param nest? string
---@diagnostic disable-next-line:lowercase-global
@@ -552,9 +512,11 @@ function printinherits(ent, nest)
end
end
----Get the original metatable that Valve assigns to the entity.
----@param ent EntityClass # The entity search.
----@return table? # Metatable originally assigned to `ent`.
+---
+---Gets the original metatable that Valve assigns to the entity.
+---
+---@param ent EntityClass # The entity search
+---@return table? # Metatable originally assigned to `ent`
---@diagnostic disable-next-line:lowercase-global
function getvalvemeta(ent)
local inherits = rawget(ent, "__inherits")
@@ -567,10 +529,14 @@ function getvalvemeta(ent)
end
end
----Get a list of all classes that `class` inherits.
----Does not include the Valve class; use getvalvemeta() for that.
----@param class EntityClass # The entity or class to search.
----@return EntityClass[] # List of class tables.
+---
+---Gets a list of all classes that `class` inherits.
+---
+---Does not include the Valve class - use [getvalvemeta](lua://getvalvemeta) for that.
+---
+---@see getvalvemeta
+---@param class EntityClass # The entity or class to search
+---@return EntityClass[] # List of class tables
---@diagnostic disable-next-line:lowercase-global
function getinherits(class)
local foundinherits = {}
@@ -587,10 +553,13 @@ function getinherits(class)
return foundinherits
end
----Get if an `EntityClass` instance inherits a given `class`.
----@param ent EntityClass|EntityHandle # Entity to check.
----@param class string|table # Name or class table to check.
----@return boolean # True if `ent` inherits `class`, false otherwise.
+---
+---Checks if an entity inherits a given `EntityClass`.
+---@param ent EntityClass|EntityHandle # Entity to check
+---@param class string|table # Name or class table to check
+---@return boolean # `true` if `ent` inherits `class`, `false` otherwise
+---@see getinherits
+---@see IsClassEntity
---@diagnostic disable-next-line:lowercase-global
function isinstance(ent, class)
if not IsEntity(ent, true) then return false end
@@ -617,10 +586,11 @@ function isinstance(ent, class)
end
---
----Check if an entity is using the AlyxLib class system.
+---Checks if an entity is using the AlyxLib class system.
---
----@param ent EntityHandle # Entity to check.
----@return boolean # True if `ent` is a class entity, false otherwise.
+---@param ent EntityHandle # Entity to check
+---@return boolean # `true` if `ent` is a class entity, `false` otherwise
+---@see isinstance
function IsClassEntity(ent)
local name = rawget(ent, "__name")
return type(name) == "string" and EntityClassNameMap[name] ~= nil
@@ -629,12 +599,11 @@ end
---
---Binds a class to an already spawned entity so it remains attached between game loads.
---
----@param entity EntityHandle # Entity to bind to.
----@param script string|table # Script, class, or class name to bind.
----@param activateType any
+---@param entity EntityHandle # Entity to bind to
+---@param script string|table # Script, class, or class name to bind
+---@param activateType? ActivationType
local function doClassBind(entity, script, activateType)
local scope = entity:GetOrCreatePrivateScriptScope()
- print("inheriting", scope, script)
inherit(script, scope)
if activateType and type(scope.Activate) == "function" then
scope.Activate(activateType)
@@ -644,8 +613,8 @@ end
---
---Binds a class to an already spawned entity so it remains attached between game loads.
---
----@param entity EntityHandle # Entity to bind to.
----@param script string # Script, class, or class name to bind.
+---@param entity EntityHandle # Entity to bind to
+---@param script string # Script, class, or class name to bind
function BindClass(entity, script)
doClassBind(entity, script, 0)
entity:SetContext("BoundEntityClassScript", script, 0)
diff --git a/scripts/vscripts/alyxlib/controls/gesture.lua b/scripts/vscripts/alyxlib/controls/gesture.lua
index 444c3b6..651a5e9 100644
--- a/scripts/vscripts/alyxlib/controls/gesture.lua
+++ b/scripts/vscripts/alyxlib/controls/gesture.lua
@@ -4,9 +4,9 @@
Provides a system for tracking simple hand poses and gestures.
- If not using `vscripts/alyxlib/init.lua`, load this file at game start using the following line:
+ If not using `alyxlib/init.lua`, load this file at game start using the following line:
- require "alyxlib.input.gesture"
+ require "alyxlib.controls.gesture"
]]
---@TODO: Refine finger tracking positions, especially thumb.
@@ -28,24 +28,37 @@
---| "Peace"
-- Custom gesture names can be appended here to improve writing.
+---
+---Gesture table.
+---
---@class GestureTable
----@field name GestureNames # Name of the gesture.
----@field index number|nil # [0-1] range of the index finger position.
----@field middle number|nil # [0-1] range of the middle finger position.
----@field ring number|nil # [0-1] range of the ring finger position.
----@field pinky number|nil # [0-1] range of the pinky finger position.
----@field thumb number|nil # [0-1] range of the thumb finger position.
+---@field name GestureNames # Gesture name
+---@field index number|nil # [0–1] range of the index finger position
+---@field middle number|nil # [0–1] range of the middle finger position
+---@field ring number|nil # [0–1] range of the ring finger position
+---@field pinky number|nil # [0–1] range of the pinky finger position
+---@field thumb number|nil # [0–1] range of the thumb finger position
+---
+---Provides a system for tracking simple hand poses and gestures.
+---
Gesture = {}
Gesture.__index = Gesture
+
+---
+---Current version of the gesture system.
+---
Gesture.version = "v1.2.0"
---
---If the gesture system should start automatically on player spawn.
----Set this to false soon after require to stop it.
+---This is false by default.
---
Gesture.AutoStart = false
+---
+---If the gesture system should print debug messages.
+---
Gesture.DebugEnabled = false
---
@@ -77,19 +90,22 @@ Gesture.PreviousGesture = {
[1] = nil,
}
+---
+---How close the fingers need to be to the gesture values for it to be considered active.
+---
Gesture.DiscrepancyTolerance = 0.25
---
----Create a new gesture table.
----If finger position is nil then the finger isn't taken into consideration.
+---Creates a new gesture table.
+---If a finger position is `nil` then the finger isn't taken into consideration.
---
----@param name? string
----@param index number|nil
----@param middle number|nil
----@param ring number|nil
----@param pinky number|nil
----@param thumb number|nil
----@return GestureTable
+---@param name? string # Gesture name
+---@param index number|nil # [0–1] range of the index finger position
+---@param middle number|nil # [0–1] range of the middle finger position
+---@param ring number|nil # [0–1] range of the ring finger position
+---@param pinky number|nil # [0–1] range of the pinky finger position
+---@param thumb number|nil # [0–1] range of the thumb finger position
+---@return GestureTable # New gesture table
local function getGestureTable(name, index, middle, ring, pinky, thumb)
return
{
@@ -120,16 +136,16 @@ local callbacks = {
}
---
----Add a new gesture to watch for.
+---Adds a new gesture to watch for.
---
----If a finger position is nil then the finger isn't taken into consideration.
+---If a finger position is `nil`, that finger isn't considered.
---
----@param name string # Name of the gesture. If not unique it will overwrite the previous gesture with this name.
----@param index number|nil # [0-1] range of the index finger position.
----@param middle number|nil # [0-1] range of the middle finger position.
----@param ring number|nil # [0-1] range of the ring finger position.
----@param pinky number|nil # [0-1] range of the pinky finger position.
----@param thumb number|nil # [0-1] range of the thumb finger position.
+---@param name string # Gesture name; overwrites existing one if not unique
+---@param index number|nil # [0–1] range of the index finger position.
+---@param middle number|nil # [0–1] range of the middle finger position.
+---@param ring number|nil # [0–1] range of the ring finger position.
+---@param pinky number|nil # [0–1] range of the pinky finger position.
+---@param thumb number|nil # [0–1] range of the thumb finger position.
function Gesture:AddGesture(name, index, middle, ring, pinky, thumb)
Gesture.Gestures[name] = getGestureTable(name, index, middle, ring, pinky, thumb)
callbacks[0].start[name] = {}
@@ -139,11 +155,11 @@ function Gesture:AddGesture(name, index, middle, ring, pinky, thumb)
end
---
----Remove an existing gesture.
+---Removes an existing gesture.
---
---Any callbacks registered with the gesture will be unregistered.
---
----@param name string # Name of the gesture.
+---@param name string # Gesture name
function Gesture:RemoveGesture(name)
Gesture.Gestures[name] = nil
callbacks[0].start[name] = nil
@@ -153,9 +169,9 @@ function Gesture:RemoveGesture(name)
end
---
----Remove a list of gestures.
+---Removes a list of gestures.
---
----@param names GestureNames[]
+---@param names GestureNames[] # List of gesture names
function Gesture:RemoveGestures(names)
for _,name in ipairs(names) do
self:RemoveGesture(name)
@@ -186,8 +202,8 @@ Gesture:AddGesture("Peace", 1, 1, 0, 0, nil)
--- do_something()
--- end
---
----@param hand CPropVRHand|0|1
----@return GestureNames
+---@param hand CPropVRHand|0|1 # The hand to get the gesture of
+---@return GestureNames # Gesture name
function Gesture:GetGesture(hand)
if type(hand) ~= "number" then
hand = hand:GetHandID()
@@ -198,10 +214,10 @@ function Gesture:GetGesture(hand)
end
---
----Gets the current [0-1] finger curl values of a given hand.
+---Gets the current [0–1] finger curl values of a given hand.
---
----@param hand CPropVRHand|0|1
----@return GestureTable
+---@param hand CPropVRHand|0|1 # The hand to get the gesture of
+---@return GestureTable # Gesture values
function Gesture:GetGestureRaw(hand)
if type(hand) ~= "number" then
hand = hand:GetHandID()
@@ -210,14 +226,14 @@ function Gesture:GetGestureRaw(hand)
end
---
----Register a callback for a specific gesture start/stop.
+---Registers a callback for a specific gesture start/stop.
---
----@param kind "start"|"stop" # If the callback is registered for gesture start or stop.
----@param hand CPropVRHand|-1|0|1 # The ID of the hand to register for (-1 means both).
----@param gesture GestureNames # Name of the gesture.
+---@param kind "start"|"stop" # Listen for the start or stop of the gesture
+---@param hand CPropVRHand|-1|0|1 # The hand to listen to
+---@param gesture GestureNames # The gesture to listen for
---@param duration unknown # Not implemented
----@param callback function # The function that will be called when conditions are met.
----@param context? any
+---@param callback function # The function to call when the gesture is triggered
+---@param context? any # Context to pass to the callback
function Gesture:RegisterCallback(kind, hand, gesture, duration, callback, context)
-- Quick way to register both hands.
@@ -234,9 +250,9 @@ function Gesture:RegisterCallback(kind, hand, gesture, duration, callback, conte
end
---
----Unregister a callback function.
+---Unregisters a callback function.
---
----@param callback function
+---@param callback function # The function to unregister
function Gesture:UnregisterCallback(callback)
for _, kinds in pairs(callbacks) do
for _, names in pairs(kinds) do
@@ -278,10 +294,10 @@ local pinky_min = 1.3178
local gestures = Gesture.Gestures
---@class GESTURE_CALLBACK
----@field kind "start"|"stop" # If the gesture was started or stopped.
----@field name GestureNames # The name of the gesture performed.
----@field hand CPropVRHand # The hand the gesture was performed on.
----@field time number # Server time the gesture occurred.
+---@field kind "start"|"stop" # If the gesture was started or stopped
+---@field name GestureNames # The name of the gesture performed
+---@field hand CPropVRHand # The hand the gesture was performed on
+---@field time number # Server time the gesture occurred
local function GestureThink()
local hmd = Entities:GetLocalPlayer():GetHMDAvatar()--[[@as CPropHMDAvatar]]
@@ -411,7 +427,7 @@ local tracking_ent = nil
---
---Starts the gesture system.
---
----@param on EntityHandle? # Optional entity to do the tracking on. This is the player by default.
+---@param on EntityHandle? # Optional entity to do the tracking on
function Gesture:Start(on)
if on == nil then on = Entities:GetLocalPlayer() end
tracking_ent = on
diff --git a/scripts/vscripts/alyxlib/controls/haptics.lua b/scripts/vscripts/alyxlib/controls/haptics.lua
index 31022b5..84e52fc 100644
--- a/scripts/vscripts/alyxlib/controls/haptics.lua
+++ b/scripts/vscripts/alyxlib/controls/haptics.lua
@@ -4,9 +4,9 @@
Haptic sequences allow for more complex vibrations than the one-shot pulses that the base API provides.
- If not using `vscripts/alyxlib/init.lua`, load this file at game start using the following line:
+ If not using `alyxlib/init.lua`, load this file at game start using the following line:
- require "alyxlib.input.haptics"
+ require "alyxlib.controls.haptics"
This script was adapted from PeterSHollander's haptics.lua
https://github.com/PeterSHollander/glorious_gloves/blob/master/scripts/vscripts/device/haptics.lua
@@ -15,6 +15,9 @@
local MIN_PULSE_WIDTH = 1
local MAX_PULSE_WIDTH = 30
+---
+---Haptic sequence.
+---
---@class HapticSequence
local HapticSequenceClass = {
---@type string
@@ -28,6 +31,10 @@ local HapticSequenceClass = {
pulseWidth_us = 0,
}
HapticSequenceClass.__index = HapticSequenceClass
+
+---
+---Current version of the haptics system.
+---
HapticSequenceClass.version = "v1.0.2"
---
diff --git a/scripts/vscripts/alyxlib/controls/input.lua b/scripts/vscripts/alyxlib/controls/input.lua
index aeb5e25..b0f0d6a 100644
--- a/scripts/vscripts/alyxlib/controls/input.lua
+++ b/scripts/vscripts/alyxlib/controls/input.lua
@@ -4,7 +4,7 @@
Simplifies the tracking of digital action presses/releases and analog values.
- If not using `vscripts/alyxlib/init.lua`, load this file at game start using the following line:
+ If not using `alyxlib/init.lua`, load this file at game start using the following line:
require "alyxlib.controls.input"
]]
@@ -19,7 +19,7 @@ Input.version = "v4.0.2"
---
---If the input system should start automatically on player spawn.
----Set this to false soon after require to stop it.
+---This is `true` by default.
---
Input.AutoStart = true
@@ -47,38 +47,47 @@ Input.ReleasedTolerance = 0.5
---@type number
Input.MultiplePressInterval = 0.35
----@class InputCallbackTable
----@field presses integer
----@field func function
----@field context any # Value passed into first argument of callback.
----@field handkind InputHandKind # Left, right, primary, secondary.
----@field actualhandid 0|1 # This needs to be updated whenver hands change.
----@field button DigitalInputAction # The button for this event.
----@field kind "press"|"release"
----@field press_time number
----@field prev_press_time number
----@field release_time number
----@field multiple_press_count number
-
----@class AnalogCallbackTable
----@field analog AnalogInputAction
----@field value { x: number?, y: number? }
----@field checkGreaterThan boolean
----@field func function
----@field context any # Value passed into first argument of callback.
----@field handkind InputHandKind # Left, right, primary, secondary.
----@field actualhandid 0|1 # This needs to be updated whenver hands change.
+---
+---Internal data structure for button callbacks.
+---
+---@class InputActionDataTable
+---@field presses integer # Number of times this button needs to be pressed
+---@field func function # The callback function
+---@field context any # Value passed into first argument of callback
+---@field handkind InputHandKind # The kind of hand that used the button
+---@field actualhandid 0|1 # Actual hand ID that used the button
+---@field button DigitalInputAction # The button used for this event
+---@field kind "press"|"release" # Whether the button was pressed or released
+---@field press_time number # Server time the button was pressed
+---@field prev_press_time number # Last server time the button was pressed
+---@field release_time number # Server time the button was released
+---@field multiple_press_count number # Number of times this button was pressed
+
+---
+---Internal data structure for analog callbacks.
+---
+---@class InputAnalogDataTable
+---@field analog AnalogInputAction # The analog action for this event
+---@field value { x: number?, y: number? } # The required analog value for this event
+---@field checkGreaterThan boolean # If checking for greater than `value` or less than
+---@field func function # The callback function
+---@field context any # Value passed into first argument of callback
+---@field handkind InputHandKind # The kind of hand that moved the analog
+---@field actualhandid 0|1 # Actual hand ID that moved the analog
---@field literalhandtype 0|1 # The literal value of the hand which is usually the opposite of Id
----@type table
+---Table of all button callbacks.
+---@type table
local buttonCallbacks = {}
----@type table
+---Table of all analog callbacks.
+---@type table
local analogCallbacks = {}
+---Counter for button and analog callbacks.
+---@type integer
local callbackId = 0
-
local currentPrimaryHandId = 1
local currentSecondaryHandId = 0
@@ -223,8 +232,8 @@ local ControllerTypeDescriptions = DefaultTable(
---Get the description of a given button.
---Useful for debugging or hint display.
---
----@param button DigitalInputAction
----@return string
+---@param button DigitalInputAction # The button to get the description of
+---@return string # The description string
function Input:GetButtonDescription(button)
return DigitalDescriptions[button]
end
@@ -233,8 +242,8 @@ end
---Get the description of a given analog action.
---Useful for debugging or hint display.
---
----@param analog DigitalInputAction
----@return string
+---@param analog DigitalInputAction # The analog action to get the description of
+---@return string # The description string
function Input:GetAnalogDescription(analog)
return AnalogDescriptions[analog]
end
@@ -242,8 +251,8 @@ end
---
---Get the description of a given controller type.
---
----@param controllerType ControllerType
----@return string
+---@param controllerType ControllerType # The controller type
+---@return string # The description string
function Input:GetControllerTypeDescription(controllerType)
return ControllerTypeDescriptions[controllerType]
end
@@ -251,8 +260,8 @@ end
---
---Get the name of a hand.
---
----@param hand CPropVRHand|0|1
----@param use_operant boolean? # If true, will return primary/secondary instead of left/right
+---@param hand CPropVRHand|0|1 # The hand entity or ID
+---@param use_operant boolean? # If true, name will use primary/secondary instead of right/left
function Input:GetHandName(hand, use_operant)
if type(hand) ~= "number" then
hand = hand:GetHandID()
@@ -271,18 +280,18 @@ end
--#endregion General requests
---
----Register a callback for a specific button press/release.
+---Listens to a specific digital input press/release.
---
---@generic T
----@param kind string # The kind of button interaction.
----| '"press"' # Button is pressed.
----| '"release"' # Button is released.
----@param hand CPropVRHand|`InputHandBoth`|InputHandKind # The type of hand to listen on, or the hand itself.
+---@param kind string # The kind of button interaction
+---| '"press"' # Button is pressed
+---| '"release"' # Button is released
+---@param hand CPropVRHand|`InputHandBoth`|InputHandKind # The type of hand to listen on, or the hand itself
---| -1 # Both hands
----@param button NamedDigitalInputAction|DigitalInputAction # The button to check.
----@param presses integer|nil # Number of times the button must be pressed in quick succession. E.g. 2 for double click. Only applicable for `kind` press.
----@param callback fun(params:InputPressCallback|InputReleaseCallback)|fun(context:T,params:InputPressCallback|InputReleaseCallback) # The function that will be called when conditions are met.
----@param context? T # Optional context passed into the callback as the first value. Is also used when unregistering.
+---@param button NamedDigitalInputAction|DigitalInputAction # The button to listen to
+---@param presses integer|nil # Number of times the button must be pressed in quick succession. E.g. 2 for double click. Only applicable for `kind` press
+---@param callback fun(params:InputPressCallback|InputReleaseCallback)|fun(context:T,params:InputPressCallback|InputReleaseCallback) # The function that will be called when the button is pressed
+---@param context? T # Optional context passed into the callback as the first value
function Input:ListenToButton(kind, hand, button, presses, callback, context)
if type(hand) ~= "number" then
@@ -342,14 +351,14 @@ local function getCorrectAnalogValue(analogValue)
end
---
----Listen to a specific analog value reaching a certain value.
+---Listens to a specific analog value reaching a certain value.
---
----@param kind "up"|"down" # `up` means listen for the value moving above `analogValue`, `down` means listen for it moving below.
----@param hand CPropVRHand|InputHandKind # The hand entity or kind of hand to listen to.
----@param analogAction AnalogInputAction # The specific analog action to listen for.
----@param analogValue AnalogValueType # The value(s) to listen for.
----@param callback fun(params:InputAnalogCallback) # The function that will be called when conditions are met.
----@param context? any # Optional context passed into the callback as the first value. Is also used when unregistering.
+---@param kind "up"|"down" # `up` means listen for the value moving above `analogValue`, `down` means listen for it moving below
+---@param hand CPropVRHand|InputHandKind # The hand entity or kind of hand to listen on
+---@param analogAction AnalogInputAction # The specific analog action to listen for
+---@param analogValue AnalogValueType # The value(s) to listen for
+---@param callback fun(params:InputAnalogCallback) # The function that will be called when conditions are met
+---@param context? any # Optional context passed into the callback as the first value
function Input:ListenToAnalog(kind, hand, analogAction, analogValue, callback, context)
local handid = convertHandKindToHandId(hand)
@@ -373,10 +382,10 @@ end
---
---Changes some data which was defined in `ListenToAnalog` for a specific ID.
---
----@param id integer # The ID of the analog event you want to modify.
----@param analogAction? AnalogInputAction # The new action to listen for, or nil to leave unchanged.
----@param analogValue AnalogValueType # The new value to listen for, or nil to leave unchanged.
----@return boolean # True if the ID was found, false otherwise.
+---@param id integer # The ID of the analog event you want to modify
+---@param analogAction? AnalogInputAction # The new action to listen for, or `nil` to leave unchanged
+---@param analogValue AnalogValueType # The new value to listen for, or `nil` to leave unchanged
+---@return boolean # True if the ID was found, false otherwise
function Input:ModifyAnalogCallback(id, analogAction, analogValue)
for _id, analogCallbackData in pairs(analogCallbacks) do
if _id == id then
@@ -396,7 +405,9 @@ end
---
---Unregisters a listener with a specific ID.
---
----@param id number # The number returned by ListenToButton.
+---@see Input.ListenToButton
+---@see Input.ListenToAnalog
+---@param id number # The ID of the listener
function Input:StopListening(id)
for _id, tbl in pairs(buttonCallbacks) do
if _id == id then
@@ -416,8 +427,8 @@ end
---
---Unregisters any listeners with a specific callback/context pair.
---
----@param callback fun(params:InputAnalogCallback) # The callback function that's listening.
----@param context? any # The context that was given.
+---@param callback fun(params:InputAnalogCallback) # The callback function
+---@param context? any # The context that was given
function Input:StopListeningCallbackContext(callback, context)
for _id, tbl in pairs(buttonCallbacks) do
if tbl.func == callback and tbl.context == context then
@@ -437,7 +448,7 @@ end
---
---Unregisters any listeners which have a specific context.
---
----@param context any # The number returned by ListenToButton.
+---@param context any # The context that was given
function Input:StopListeningByContext(context)
for _id, tbl in pairs(buttonCallbacks) do
if tbl.context == context then
@@ -452,24 +463,32 @@ function Input:StopListeningByContext(context)
end
end
-
+---
+---A callback for when a button is pressed.
+---
---@class InputPressCallback
----@field kind "press" # The kind of event.
----@field press_time number # The server time at which the button was pressed.
----@field hand CPropVRHand # EntityHandle for the hand that pressed the button.
----@field button DigitalInputAction # The ID of the button that was pressed.
+---@field kind "press" # The kind of event
+---@field press_time number # The server time at which the button was pressed
+---@field hand CPropVRHand # EntityHandle for the hand that pressed the button
+---@field button DigitalInputAction # The ID of the button that was pressed
+---
+---A callback for when a button is released.
+---
---@class InputReleaseCallback
----@field kind "release" # The kind of event.
----@field release_time number # The server time at which the button was released.
----@field hand CPropVRHand # EntityHandle for the hand that released the button.
----@field button DigitalInputAction # The ID of the button that was pressed.
----@field held_time number # Seconds the button was held for prior to being released.
+---@field kind "release" # The kind of event
+---@field release_time number # The server time at which the button was released
+---@field hand CPropVRHand # EntityHandle for the hand that released the button
+---@field button DigitalInputAction # The ID of the button that was pressed
+---@field held_time number # Seconds the button was held for prior to being released
+---
+---A callback for when an analog action is moved.
+---
---@class InputAnalogCallback
----@field value Vector # The vector value of the analog action at the time of detection.
----@field hand CPropVRHand # EntityHandle for the hand that moved the analog action.
----@field analog AnalogInputAction # The ID of the analog action that was moved.
+---@field value Vector # The vector value of the analog action at the time of detection
+---@field hand CPropVRHand # EntityHandle for the hand that moved the analog action
+---@field analog AnalogInputAction # The ID of the analog action that was moved
---@alias INPUT_CALLBACK InputPressCallback|InputReleaseCallback
@@ -598,7 +617,7 @@ local tracking_ent = nil
---
---Starts the input system.
---
----@param on EntityHandle? # Optional entity to do the tracking on. This is the player by default.
+---@param on EntityHandle? # Optional entity to do the tracking on. This is the player by default
function Input:Start(on)
if on == nil then on = Entities:GetLocalPlayer() end
tracking_ent = on
diff --git a/scripts/vscripts/alyxlib/data/color.lua b/scripts/vscripts/alyxlib/data/color.lua
index 2943a1e..2a000c4 100644
--- a/scripts/vscripts/alyxlib/data/color.lua
+++ b/scripts/vscripts/alyxlib/data/color.lua
@@ -2,7 +2,7 @@
v1.0.2
https://github.com/FrostSource/alyxlib
- If not using `vscripts/alyxlib/init.lua`, load this file at game start using the following line:
+ If not using `alyxlib/init.lua`, load this file at game start using the following line:
require "alyxlib.data.color"
]]
@@ -32,10 +32,10 @@ local function resolveColorRange(value, default)
end
---Converts a hue value to an RGB value.
----@param p number # The value used in the RGB calculation.
----@param q number # The value used in the RGB calculation.
----@param t number # The hue value to convert to RGB.
----@return number # The RGB value corresponding to the given hue.
+---@param p number # The value used in the RGB calculation
+---@param q number # The value used in the RGB calculation
+---@param t number # The hue value to convert to RGB
+---@return number # The RGB value corresponding to the given hue
local function hueToRgb(p, q, t)
if t < 0 then t = t + 1 end
if t > 1 then t = t - 1 end
@@ -46,12 +46,12 @@ local function hueToRgb(p, q, t)
end
---Converts HSL color values to RGB.
----@param h number # Hue value range [0-1].
----@param s number # Saturation value range [0-1].
----@param l number # Lightness value range [0-1].
----@return integer r # Red color value in range [0-255].
----@return integer g # Green color value in range [0-255].
----@return integer b # Blue color value in range [0-255].
+---@param h number # Hue value range [0-1]
+---@param s number # Saturation value range [0-1]
+---@param l number # Lightness value range [0-1]
+---@return integer r # Red color value in range [0-255]
+---@return integer g # Green color value in range [0-255]
+---@return integer b # Blue color value in range [0-255]
local function hslToRgb(h, s, l)
local r, g, b
@@ -73,12 +73,12 @@ local function hslToRgb(h, s, l)
end
---Converts RGB color values to HSL.
----@param r number # Red value range [0-255].
----@param g number # Red value range [0-255].
----@param b number # Red value range [0-255].
----@return number h # Hue color value in range [0-1].
----@return number s # Saturation color value in range [0-1].
----@return number l # Lightness color value in range [0-1].
+---@param r number # Red value range [0-255]
+---@param g number # Red value range [0-255]
+---@param b number # Red value range [0-255]
+---@return number h # Hue color value in range [0-1]
+---@return number s # Saturation color value in range [0-1]
+---@return number l # Lightness color value in range [0-1]
local function rgbToHsl(r, g, b)
r = r / 255
g = g / 255
@@ -103,13 +103,13 @@ end
---Represents a color object with RGB and HSL components.
---@class Color
----@field r integer # Red component.
----@field g integer # Green component.
----@field b integer # Blue component.
----@field a integer # Alpha component.
----@field hue integer # Hue component.
----@field saturation integer # Saturation component.
----@field lightness integer # Lightness component.
+---@field r integer # Red component
+---@field g integer # Green component
+---@field b integer # Blue component
+---@field a integer # Alpha component
+---@field hue integer # Hue component
+---@field saturation integer # Saturation component
+---@field lightness integer # Lightness component
local ColorClass = {}
if Storage then
@@ -120,10 +120,10 @@ if Storage then
---
---Helper function for saving the `Color`.
---
- ---@param handle EntityHandle # The entity to save on.
- ---@param name string # The name to save as.
- ---@param color Color # The color to save.
- ---@return boolean # If the save was successful.
+ ---@param handle EntityHandle # The entity to save on
+ ---@param name string # The name to save as
+ ---@param color Color # The color to save
+ ---@return boolean # If the save was successful
---@luadoc-ignore
function ColorClass.__save(handle, name, color)
return Storage.SaveTableCustom(handle, name, color, "Color")
@@ -134,8 +134,8 @@ if Storage then
---
---Helper function for loading the `Color`.
---
- ---@param handle EntityHandle # Entity to load from.
- ---@param name string # Name to load.
+ ---@param handle EntityHandle # Entity to load from
+ ---@param name string # Name to load
---@return Color|nil
---@luadoc-ignore
function ColorClass.__load(handle, name)
@@ -152,10 +152,10 @@ if Storage then
---Load a `Color`.
---
---@generic T
- ---@param handle EntityHandle # Entity to load from.
- ---@param name string # Name the Color was saved as.
- ---@param default? T # Optional default value.
- ---@return Color|T
+ ---@param handle EntityHandle # Entity to load from
+ ---@param name string # Name the Color was saved as
+ ---@param default? T # Optional default value
+ ---@return Color|T # The loaded Color
---@luadoc-ignore
Storage.LoadColor = function(handle, name, default)
local color = ColorClass.__load(handle, name)
@@ -231,9 +231,10 @@ end
---
---Converts this `Color` to a hexadecimal representation.
----The hexadecimal format is in the format #RRGGBB.
---
----@return string # The hexadecimal representation of this `Color`.
+---The hexadecimal string is in the format #RRGGBB.
+---
+---@return string # The hexadecimal representation of this [Color](lua://Color)
function ColorClass:ToHexString()
local function toHex(val)
return string.format("%02X", val)
@@ -242,28 +243,35 @@ function ColorClass:ToHexString()
return "#" .. toHex(self.r) .. toHex(self.g) .. toHex(self.b)
end
----Get a `Vector` from this `Color` in the form of [x=r, y=g, z=b].
----@return Vector # The color vector.
+---
+---Returns a [Vector](lua://Vector) from this [Color](lua://Color).
+---In the form of [x=r, y=g, z=b], with ranges [0-255].
+---
+---@return Vector # The vector representation of this [Color](lua://Color)
function ColorClass:ToVector()
return Vector(self[1], self[2], self[3])
end
----Get a `Vector` from this `Color` in the form of [x=r, y=g, z=b] but with ranges [0-1].
----@return Vector # The color vector.
+---
+---Returns a [Vector](lua://Vector) from this [Color](lua://Color).
+---In the form of [x=r, y=g, z=b], with ranges [0-1].
+---
+---@return Vector # The decimal vector representation of this [Color](lua://Color)
function ColorClass:ToDecimalVector()
return Vector(self[1] / 255, self[2] / 255, self[3] / 255)
end
---
----Sets the color based on the provided RGB (Red, Green, Blue) components and an optional alpha component.
----If any of the provided values have fractional parts, they will all be normalized to the range [0, 255].
+---Sets the RGBA components of this [Color](lua://Color).
+---
+---If any of the provided values have fractional parts, they will all be normalized to the range [0, 255].
---If any of the provided values are nil or omitted, the corresponding component of the color will remain unchanged.
---
----@param r? number # The red component of the color.
----@param g? number # The green component of the color.
----@param b? number # The blue component of the color.
----@param a? number # The alpha component of the color.
+---@param r? number # The red component of the color
+---@param g? number # The green component of the color
+---@param b? number # The blue component of the color
+---@param a? number # The alpha component of the color
function ColorClass:SetRGB(r, g, b, a)
if hasfrac(r) or hasfrac(g) or hasfrac(b) or hasfrac(a) then
r = r and r * 255 or self.r
@@ -278,7 +286,9 @@ function ColorClass:SetRGB(r, g, b, a)
self.a = a
end
----Get the HSL color values from this `Color`.
+---
+---Gets the HSL color values for this [Color](lua://Color).
+---
---@return number h # Hue color value in range [0-360]
---@return number s # Saturation color value in range [0-100]
---@return number l # Lightness color value in range [0-100]
@@ -288,14 +298,14 @@ function ColorClass:GetHSL()
end
---
----Sets the color based on the provided HSL (Hue, Saturation, Lightness) components.
----The method accepts values for hue, saturation, and lightness in their respective ranges and updates the color accordingly.
+---Sets the HSL (Hue, Saturation, Lightness) components of this [Color](lua://Color).
---
----If any of the provided values have fractional parts, they will be normalized to their appropriate ranges (0 to 360 for hue, 0 to 100 for saturation and lightness).
+---If any of the provided values have fractional parts, they will be normalized to their appropriate ranges (0 to 360 for hue, 0 to 100 for saturation and lightness).
---If any of the provided values are nil or omitted, the corresponding component of the color will remain unchanged.
----@param h? number # The hue value of the color (0 to 360), representing the color's position on the color wheel.
----@param s? number # The saturation value of the color (0 to 100), determining the intensity of the color.
----@param l? number # The lightness value of the color (0 to 100), affecting the brightness of the color.
+---
+---@param h? number # The hue value of the color (0 to 360), representing the color's position on the color wheel
+---@param s? number # The saturation value of the color (0 to 100), determining the intensity of the color
+---@param l? number # The lightness value of the color (0 to 100), affecting the brightness of the color
function ColorClass:SetHSL(h, s, l)
if hasfrac(h) or hasfrac(s) or hasfrac(l) then
h = h and h / 360 or nil
@@ -315,13 +325,13 @@ function ColorClass:SetHSL(h, s, l)
end
---
----Create a new `Color` instance using range [0-1] or [0-255].
+---Creates a new [Color](lua://Color) instance using range [0-255] or [0-1].
---
----@param r? number # Red color value.
----@param g? number # Green color value.
----@param b? number # Blue color value.
----@param a? number # Alpha value.
----@return Color
+---@param r? number # Red color value
+---@param g? number # Green color value
+---@param b? number # Blue color value
+---@param a? number # Alpha value
+---@return Color # The new [Color](lua://Color)
---@overload fun(rgb: Vector)
---@overload fun(rgb: string)
function Color(r, g, b, a)
@@ -354,10 +364,10 @@ function Color(r, g, b, a)
end
---
----Get if a value is a `Color`.
+---Checks if a value is a `Color`.
---
----@param value any
----@return boolean
+---@param value any # The value to check
+---@return boolean # `true` if the value is a [Color](lua://Color), `false` otherwise
function IsColor(value)
return type(value) == "table" and getmetatable(value) == ColorClass
end
diff --git a/scripts/vscripts/alyxlib/data/inventory.lua b/scripts/vscripts/alyxlib/data/inventory.lua
index ebee628..2912cd5 100644
--- a/scripts/vscripts/alyxlib/data/inventory.lua
+++ b/scripts/vscripts/alyxlib/data/inventory.lua
@@ -5,13 +5,16 @@
An 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.
- If not using `vscripts/alyxlib/init.lua`, load this file at game start using the following line:
+ If not using `alyxlib/init.lua`, load this file at game start using the following line:
require "alyxlib.data.inventory"
]]
local version = "v1.2.3"
+---
+---Inventory data structure.
+---
---@class Inventory
local InventoryClass =
{
@@ -28,10 +31,10 @@ if pcall(require, "alyxlib.storage") then
---
---Helper function for saving the `inventory`.
---
- ---@param handle EntityHandle # The entity to save on.
- ---@param name string # The name to save as.
- ---@param inventory Inventory # The inventory to save.
- ---@return boolean # If the save was successful.
+ ---@param handle EntityHandle # The entity to save on
+ ---@param name string # The name to save as
+ ---@param inventory Inventory # The inventory to save
+ ---@return boolean # If the save was successful
---@luadoc-ignore
function InventoryClass.__save(handle, name, inventory)
return Storage.SaveTableCustom(handle, name, inventory, "Inventory")
@@ -42,9 +45,9 @@ if pcall(require, "alyxlib.storage") then
---
---Helper function for loading the `inventory`.
---
- ---@param handle EntityHandle # Entity to load from.
- ---@param name string # Name to load.
- ---@return Inventory|nil
+ ---@param handle EntityHandle # Entity to load from
+ ---@param name string # Name to load
+ ---@return Inventory|nil # The loaded inventory
---@luadoc-ignore
function InventoryClass.__load(handle, name)
local inventory = Storage.LoadTableCustom(handle, name, "Inventory")
@@ -56,13 +59,13 @@ if pcall(require, "alyxlib.storage") then
CBaseEntity.SaveInventory = Storage.SaveInventory
---
- ---Load an Inventory.
+ ---Loads an Inventory.
---
---@generic T
- ---@param handle EntityHandle # Entity to load from.
- ---@param name string # Name the Inventory was saved as.
- ---@param default? T # Optional default value.
- ---@return Inventory|T
+ ---@param handle EntityHandle # Entity to load from
+ ---@param name string # Name the Inventory was saved as
+ ---@param default? T # Optional default value
+ ---@return Inventory|T # The loaded Inventory
---@luadoc-ignore
Storage.LoadInventory = function(handle, name, default)
local inventory = InventoryClass.__load(handle, name)
@@ -75,11 +78,14 @@ if pcall(require, "alyxlib.storage") then
end
---
----Add a number of values to a key.
+---Increases value amount to a key.
---
----@param key any
----@param value? integer # Default is 1.
----@return number # The value of the key after adding.
+---If the key does not exist, it will be created.
+---If no value is provided, it will default to 1.
+---
+---@param key any # The key to increment
+---@param value? integer # Value to increment by
+---@return number # Value of the key after increment
function InventoryClass:Add(key, value)
value = value or 1
local current = self.items[key]
@@ -93,11 +99,11 @@ function InventoryClass:Add(key, value)
end
---
----Remove a number of values from a key.
+---Decreases the value of a key.
---
----@param key any
----@param value? integer # Default is 1.
----@return number # The value of the key after removal.
+---@param key any # The key to decrement
+---@param value? integer # Value to decrement by
+---@return number # Value of the key after decrement
function InventoryClass:Remove(key, value)
value = value or 1
local current = self.items[key]
@@ -114,10 +120,11 @@ function InventoryClass:Remove(key, value)
end
---
----Get the value associated with a key. This is *not* the same as `inv.items[key]`.
+---Gets the value associated with a key.
+---If the key does not exist, 0 is returned.
---
----@param key any
----@return integer
+---@param key any # The key to get
+---@return integer # Value of the key
function InventoryClass:Get(key)
local val = self.items[key]
if val then
@@ -127,10 +134,10 @@ function InventoryClass:Get(key)
end
---
----Get the key with the highest value and its value.
+---Gets the key with the highest value and its value.
---
----@return any # The key with the highest value.
----@return integer # The value associated with the key.
+---@return any # The key with the highest value
+---@return integer # The value associated with the key
function InventoryClass:Highest()
local best_key, best_value = nil, 0
for key, value in pairs(self.items) do
@@ -143,10 +150,10 @@ function InventoryClass:Highest()
end
---
----Get the key with the lowest value and its value.
+---Gets the key with the lowest value and its value.
---
----@return any # The key with the lowest value.
----@return integer # The value associated with the key.
+---@return any # The key with the lowest value
+---@return integer # The value associated with the key
function InventoryClass:Lowest()
local best_key, best_value = nil, nil
for key, value in pairs(self.items) do
@@ -160,7 +167,7 @@ function InventoryClass:Lowest()
end
---
----Get if the inventory contains a key with a value greater than 0.
+---Gets if the inventory contains `key` with a value greater than 0.
---
---@param key any
---@return boolean
@@ -172,10 +179,10 @@ function InventoryClass:Contains(key)
end
---
----Return the number of items in the inventory.
+---Returns the number of items in the inventory.
---
----@return integer key_sum # Total number of keys in the inventory.
----@return integer value_sum # Total number of values assigned to all keys.
+---@return integer key_sum # Total number of keys in the inventory
+---@return integer value_sum # Total number of values assigned to all keys
function InventoryClass:Length()
local key_sum,value_sum = 0,0
for _,value in pairs(self.items) do
@@ -186,9 +193,9 @@ function InventoryClass:Length()
end
---
----Get if the inventory is empty.
+---Gets if the inventory is empty.
---
----@return boolean
+---@return boolean # True if the inventory is empty
function InventoryClass:IsEmpty()
for _, _ in pairs(self.items) do
return false
@@ -211,13 +218,13 @@ end
---
----Create a new `Inventory` object.
+---Creates a new [Inventory](lua://Inventory) object.
---
----@param starting_inventory? table
----@return Inventory
-function Inventory(starting_inventory)
+---@param startingInventory? table # Starting inventory
+---@return Inventory # The new [Inventory](lua://Inventory)
+function Inventory(startingInventory)
return setmetatable({
- items = starting_inventory or {}
+ items = startingInventory or {}
},
InventoryClass)
end
diff --git a/scripts/vscripts/alyxlib/data/queue.lua b/scripts/vscripts/alyxlib/data/queue.lua
index 88e2014..b198045 100644
--- a/scripts/vscripts/alyxlib/data/queue.lua
+++ b/scripts/vscripts/alyxlib/data/queue.lua
@@ -4,13 +4,16 @@
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.
- If not using `vscripts/alyxlib/init.lua`, load this file at game start using the following line:
+ If not using `alyxlib/init.lua`, load this file at game start using the following line:
require "alyxlib.data.queue"
]]
local version = "v1.5.1"
+---
+---Queue data structure.
+---
---@class Queue
local QueueClass =
{
@@ -27,10 +30,10 @@ if pcall(require, "alyxlib.storage") then
---
---Helper function for saving the `queue`.
---
- ---@param handle EntityHandle # The entity to save on.
- ---@param name string # The name to save as.
- ---@param queue Queue # The stack to save.
- ---@return boolean # If the save was successful.
+ ---@param handle EntityHandle # The entity to save on
+ ---@param name string # The name to save as
+ ---@param queue Queue # The stack to save
+ ---@return boolean # If the save was successful
---@luadoc-ignore
function QueueClass.__save(handle, name, queue)
return Storage.SaveTableCustom(handle, name, queue, "Queue")
@@ -41,9 +44,9 @@ if pcall(require, "alyxlib.storage") then
---
---Helper function for loading the `stack`.
---
- ---@param handle EntityHandle # Entity to load from.
- ---@param name string # Name to load.
- ---@return Queue|nil
+ ---@param handle EntityHandle # Entity to load from
+ ---@param name string # Name to load
+ ---@return Queue|nil # The loaded stack
---@luadoc-ignore
function QueueClass.__load(handle, name)
local queue = Storage.LoadTableCustom(handle, name, "Queue")
@@ -55,13 +58,13 @@ if pcall(require, "alyxlib.storage") then
CBaseEntity.SaveQueue = Storage.SaveQueue
---
- ---Load a Queue.
+ ---Loads a Queue.
---
---@generic T
- ---@param handle EntityHandle # Entity to load from.
- ---@param name string # Name the Queue was saved as.
- ---@param default? T # Optional default value.
- ---@return Queue|T
+ ---@param handle EntityHandle # Entity to load from
+ ---@param name string # Name the Queue was saved as
+ ---@param default? T # Optional default value
+ ---@return Queue|T # The loaded Queue
---@luadoc-ignore
Storage.LoadQueue = function(handle, name, default)
local queue = QueueClass.__load(handle, name)
@@ -73,22 +76,23 @@ if pcall(require, "alyxlib.storage") then
CBaseEntity.LoadQueue = Storage.LoadQueue
end
-
-
---
----Add values to the queue in the order they appear.
+---Adds values to the queue in the order they are provided.
---
----@param ... any
+---@param ... any # Any number of values
function QueueClass:Enqueue(...)
- local args = {...}
- for i = #args, 1, -1 do
- table.insert(self.items, 1, args[i])
+ for i = select("#", ...), 1, -1 do
+ table.insert(self.items, 1, select(i, ...))
end
end
----Get a number of values in reverse order of the queue.
----@param count? number # Default is 1
----@return ...
+---
+---Removes and returns one or more items from the front of the queue.
+---
+---If `count` is omitted, a single item will is dequeued.
+---
+---@param count? number # Number of items to dequeue
+---@return ... # The dequeued items in the original insertion order
function QueueClass:Dequeue(count)
count = min(count or 1, #self.items)
local tbl = {}
@@ -99,10 +103,10 @@ function QueueClass:Dequeue(count)
end
---
----Peek at a number of items at the front of the queue without removing them.
+---Peeks at a number of items at the front of the queue without removing them.
---
----@param count? number # Default is 1
----@return ...
+---@param count? number # Number of items to peek at
+---@return ... # The peeked items
function QueueClass:Front(count)
count = min(count or 1, #self.items)
local tbl = {}
@@ -113,10 +117,10 @@ function QueueClass:Front(count)
end
---
----Peek at a number of items at the back of the queue without removing them.
+---Peeks at a number of items at the back of the queue without removing them.
---
----@param count? number # Default is 1
----@return ...
+---@param count? number # Number of items to peek at
+---@return ... # The peeked items
function QueueClass:Back(count)
count = min(count or 1, #self.items)
local tbl = {}
@@ -127,9 +131,9 @@ function QueueClass:Back(count)
end
---
----Remove a value from the queue regardless of its position.
+---Removes a value from the queue regardless of its position.
---
----@param value any
+---@param value any # The value to remove
function QueueClass:Remove(value)
for index, val in ipairs(self.items) do
if value == val then
@@ -140,11 +144,12 @@ function QueueClass:Remove(value)
end
---
----Move an existing value to the front of the queue.
----Only the first occurance will be moved.
+---Moves an existing value to the back of the queue.
+---
+---Only the furthest back occurance will be moved.
---
----@param value any # The value to move.
----@return boolean # True if value was found and moved.
+---@param value any # The value to move
+---@return boolean # True if value was found and moved
function QueueClass:MoveToBack(value)
for index, val in ipairs(self.items) do
if value == val then
@@ -157,11 +162,12 @@ function QueueClass:MoveToBack(value)
end
---
----Move an existing value to the bottom of the stack.
----Only the first occurance will be moved.
+---Moves an existing value to the front of the queue.
---
----@param value any # The value to move.
----@return boolean # True if value was found and moved.
+---Only the furthest back occurance will be moved.
+---
+---@param value any # The value to move
+---@return boolean # True if value was found and moved
function QueueClass:MoveToFront(value)
for index, val in ipairs(self.items) do
if value == val then
@@ -174,25 +180,26 @@ function QueueClass:MoveToFront(value)
end
---
----Get if this queue contains a value.
+---Gets if this queue contains a value.
---
----@param value any
----@return boolean
+---@param value any # The value to search for
+---@return boolean # True if the value is in the queue
function QueueClass:Contains(value)
return vlua.find(self.items, value) ~= nil
end
---
----Return the number of items in the queue.
+---Returns the number of items in the queue.
---
----@return integer
+---@return integer # The number of items
function QueueClass:Length()
return #self.items
end
---
----Get if the stack is empty.
+---Gets if the stack is empty.
---
+---@return boolean # True if the stack is empty
function QueueClass:IsEmpty()
return #self.items == 0
end
@@ -213,7 +220,8 @@ end
---
----Create a new `Queue` object.
+---Create a new [Queue](lua://Queue) instance.
+---
---Last value is at the front of the queue.
---
---E.g.
@@ -224,8 +232,8 @@ end
--- "Front"
--- )
---
----@param ... any
----@return Queue
+---@param ... any # Starting values
+---@return Queue # The new queue
function Queue(...)
return setmetatable({
items = {...}
diff --git a/scripts/vscripts/alyxlib/data/stack.lua b/scripts/vscripts/alyxlib/data/stack.lua
index 58269b3..f098fa7 100644
--- a/scripts/vscripts/alyxlib/data/stack.lua
+++ b/scripts/vscripts/alyxlib/data/stack.lua
@@ -4,13 +4,16 @@
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.
- If not using `vscripts/alyxlib/init.lua`, load this file at game start using the following line:
+ If not using `alyxlib/init.lua`, load this file at game start using the following line:
require "alyxlib.data.stack"
]]
local version = "v1.2.4"
+---
+---Stack data structure.
+---
---@class Stack
local StackClass =
{
@@ -27,10 +30,10 @@ if pcall(require, "alyxlib.storage") then
---
---Helper function for saving the `stack`.
---
- ---@param handle EntityHandle # The entity to save on.
- ---@param name string # The name to save as.
- ---@param stack Stack # The stack to save.
- ---@return boolean # If the save was successful.
+ ---@param handle EntityHandle # The entity to save on
+ ---@param name string # The name to save as
+ ---@param stack Stack # The stack to save
+ ---@return boolean # If the save was successful
---@luadoc-ignore
function StackClass.__save(handle, name, stack)
return Storage.SaveTableCustom(handle, name, stack, "Stack")
@@ -41,9 +44,9 @@ if pcall(require, "alyxlib.storage") then
---
---Helper function for loading the `stack`.
---
- ---@param handle EntityHandle # Entity to load from.
- ---@param name string # Name to load.
- ---@return Stack|nil
+ ---@param handle EntityHandle # Entity to load from
+ ---@param name string # Name to load
+ ---@return Stack|nil # The loaded stack
---@luadoc-ignore
function StackClass.__load(handle, name)
local stack = Storage.LoadTableCustom(handle, name, "Stack")
@@ -55,13 +58,13 @@ if pcall(require, "alyxlib.storage") then
CBaseEntity.SaveStack = Storage.SaveStack
---
- ---Load a Stack.
+ ---Loads a Stack.
---
---@generic T
- ---@param handle EntityHandle # Entity to load from.
- ---@param name string # Name the Stack was saved as.
- ---@param default? T # Optional default value.
- ---@return Stack|T
+ ---@param handle EntityHandle # Entity to load from
+ ---@param name string # Name the Stack was saved as
+ ---@param default? T # Optional default value
+ ---@return Stack|T # The loaded stack
---@luadoc-ignore
Storage.LoadStack = function(handle, name, default)
local stack = StackClass.__load(handle, name)
@@ -75,19 +78,19 @@ end
---
----Push values to the stack.
+---Pushes values to the stack.
---
----@param ... any
+---@param ... any # Any number of values
function StackClass:Push(...)
- for _, value in ipairs({...}) do
- table.insert(self.items, 1, value)
+ for i = 1, select("#", ...) do
+ table.insert(self.items, 1, select(i, ...))
end
end
---
----Pop a number of items from the stack.
+---Pops values from the stack.
---
----@param count? number # Default is 1
+---@param count? number # Number of items to pop
---@return ...
function StackClass:Pop(count)
count = min(count or 1, #self.items)
@@ -99,9 +102,9 @@ function StackClass:Pop(count)
end
---
----Peek at a number of items at the top of the stack without removing them.
+---Peeks at a number of items on the top of the stack without removing them.
---
----@param count? number # Default is 1
+---@param count? number # Number of items to peek
---@return ...
function StackClass:Top(count)
count = min(count or 1, #self.items)
@@ -113,9 +116,9 @@ function StackClass:Top(count)
end
---
----Peek at a number of items at the bottom of the stack without removing them.
+---Peeks at a number of items on the bottom of the stack without removing them.
---
----@param count? number # Default is 1
+---@param count? number # Number of items to peek
---@return ...
function StackClass:Bottom(count)
count = min(count or 1, #self.items)
@@ -127,9 +130,9 @@ function StackClass:Bottom(count)
end
---
----Remove a value from the stack regardless of its position.
+---Removes a value from the stack regardless of its position.
---
----@param value any
+---@param value any # The value to remove
function StackClass:Remove(value)
for index, val in ipairs(self.items) do
if value == val then
@@ -140,11 +143,12 @@ function StackClass:Remove(value)
end
---
----Move an existing value to the top of the stack.
+---Moves an existing value to the top of the stack.
+---
---Only the first occurance will be moved.
---
----@param value any # The value to move.
----@return boolean # True if value was found and moved.
+---@param value any # The value to move
+---@return boolean # True if value was found and moved
function StackClass:MoveToTop(value)
for index, val in ipairs(self.items) do
if value == val then
@@ -157,11 +161,12 @@ function StackClass:MoveToTop(value)
end
---
----Move an existing value to the bottom of the stack.
+---Moves an existing value to the bottom of the stack.
+---
---Only the first occurance will be moved.
---
----@param value any # The value to move.
----@return boolean # True if value was found and moved.
+---@param value any # The value to move
+---@return boolean # True if value was found and moved
function StackClass:MoveToBottom(value)
for index, val in ipairs(self.items) do
if value == val then
@@ -174,25 +179,26 @@ function StackClass:MoveToBottom(value)
end
---
----Get if this stack contains a value.
+---Gets if this stack contains a value.
---
----@param value any
----@return boolean
+---@param value any # The value to search for
+---@return boolean # True if the value is in the stack
function StackClass:Contains(value)
return vlua.find(self.items, value) ~= nil
end
---
----Return the number of items in the stack.
+---Returns the number of items in the stack.
---
----@return integer
+---@return integer # The number of items
function StackClass:Length()
return #self.items
end
---
----Get if the stack is empty.
+---Gets if the stack is empty.
---
+---@return boolean # True if the stack is empty
function StackClass:IsEmpty()
return #self.items == 0
end
@@ -213,7 +219,8 @@ end
---
----Create a new `Stack` object.
+---Creates a new [Stack](lua://Stack) object.
+---
---First value is at the top.
---
---E.g.
diff --git a/scripts/vscripts/alyxlib/debug/commands.lua b/scripts/vscripts/alyxlib/debug/commands.lua
index 9340051..6668afa 100644
--- a/scripts/vscripts/alyxlib/debug/commands.lua
+++ b/scripts/vscripts/alyxlib/debug/commands.lua
@@ -2,7 +2,7 @@
v1.1.1
https://github.com/FrostSource/alyxlib
- If not using `vscripts/alyxlib/init.lua`, load this file at game start using the following line:
+ If not using `alyxlib/init.lua`, load this file at game start using the following line:
require "alyxlib.debug.commands"
]]
@@ -14,10 +14,10 @@ local alyxlibCommands = {}
---
---Registers a command for the AlyxLib library.
---
----@param name string # Name of the command that will be given in the console.
----@param func fun(_:string, ...:string) # Function to run when the command is called.
----@param helpText? string # Description of the command.
----@param flags? number # Flags for the command.
+---@param name string # Name of the command that will be given in the console
+---@param func fun(_:string, ...:string) # Function to run when the command is called
+---@param helpText? string # Description of the command
+---@param flags? number # Flags for the command
function RegisterAlyxLibCommand(name, func, helpText, flags)
helpText = helpText or "[No description]"
Convars:RegisterCommand(name, func, helpText, flags or 0)
@@ -27,11 +27,11 @@ end
---
---Registers a new AlyxLib console variable.
---
----@param name string # Name of the convar that will be given in the console.
----@param defaultValue string|function # Default value of the convar or initializer function.
----@param helpText? string # Description of the convar.
----@param flags? integer # Flags for the convar.
----@param callback? fun(newVal:string, oldVal:string): any # Update function called after the value has been changed.
+---@param name string # Name of the convar that will be given in the console
+---@param defaultValue string|function # Default value of the convar or initializer function
+---@param helpText? string # Description of the convar
+---@param flags? integer # Flags for the convar
+---@param callback? fun(newVal:string, oldVal:string): any # Update function called after the value has been changed
function RegisterAlyxLibConvar(name, defaultValue, helpText, flags, callback)
helpText = helpText or "[No description]"
flags = flags or 0
@@ -46,12 +46,12 @@ end
---
---Registers a new AlyxLib console variable.
---
----@param name string # Name of the convar that will be given in the console.
----@param defaultValue string # Default value of the convar.
----@param helpText? string # Description of the convar.
----@param flags? integer # Flags for the convar.
----@param postUpdate? fun(newVal:string, oldVal:string): any # Update function called after the value has been changed.
----@param persistent? boolean # Whether the convar should be persistent.
+---@param name string # Name of the convar that will be given in the console
+---@param defaultValue string # Default value of the convar
+---@param helpText? string # Description of the convar
+---@param flags? integer # Flags for the convar
+---@param postUpdate? fun(newVal:string, oldVal:string): any # Update function called after the value has been changed
+---@param persistent? boolean # Whether the convar should be persistent
function RegisterAlyxLibEasyConvar(name, defaultValue, helpText, flags, postUpdate, persistent)
helpText = helpText or "[No description]"
EasyConvars:RegisterConvar(name, defaultValue, helpText, flags or 0, postUpdate)
diff --git a/scripts/vscripts/alyxlib/debug/common.lua b/scripts/vscripts/alyxlib/debug/common.lua
index eebc9ca..840d2b0 100644
--- a/scripts/vscripts/alyxlib/debug/common.lua
+++ b/scripts/vscripts/alyxlib/debug/common.lua
@@ -4,7 +4,7 @@
Debug utility functions.
- If not using `vscripts/alyxlib/init.lua`, load this file at game start using the following line:
+ If not using `alyxlib/init.lua`, load this file at game start using the following line:
require "alyxlib.debug.common"
]]
@@ -20,9 +20,9 @@ Debug.version = "v2.2.0"
---
---`pattern` can also be an entity handle string, e.g. `0x0026caf8`
---
----@param pattern string # The search pattern to look for.
----@param exact boolean? # If true the pattern must match exactly, otherwise wildcards will be used.
----@return EntityHandle? # The found entity, or nil if not found.
+---@param pattern string # The search pattern to look for
+---@param exact boolean? # If true the pattern must match exactly, otherwise wildcards will be used
+---@return EntityHandle? # The found entity, or nil if not found
function Debug.FindEntityByPattern(pattern, exact)
if pattern == nil then
@@ -63,9 +63,9 @@ entfind = Debug.FindEntityByPattern
---
---`pattern` can also be an entity handle string, e.g. `0x0026caf8`
---
----@param pattern string # The search pattern to look for.
----@param exact boolean? # If true the pattern must match exactly, otherwise wildcards will be used.
----@return EntityHandle[]
+---@param pattern string # The search pattern to look for
+---@param exact boolean? # If true the pattern must match exactly, otherwise wildcards will be used
+---@return EntityHandle[] # The found entities
function Debug.FindAllEntitiesByPattern(pattern, exact)
local ents = {}
@@ -88,17 +88,17 @@ function Debug.FindAllEntitiesByPattern(pattern, exact)
end
---
----Prints a formated indexed list of entities with custom property information.
+---Prints a formated indexed list of entities with custom property information.
---Also links children with their parents by displaying the index alongside the parent for easy look-up.
---
--- Debug.PrintEntityList(ents, {"getclassname", "getname", "getname"})
---
----If no properties are supplied the default properties are used: GetClassname, GetName, GetModelName
----If an empty property table is supplied only the base values are shown: Index, Handle, Parent
+---If no properties are supplied the default properties are used: GetClassname, GetName, GetModelName.
+---If an empty property table is supplied only the base values are shown: Index, Handle, Parent.
---Property patterns do not need to be functions.
---
----@param list EntityHandle[] # List of entities to print.
----@param properties? string[] # List of property patterns to search for.
+---@param list EntityHandle[] # List of entities to print
+---@param properties? string[] # List of property patterns to search for
function Debug.PrintEntityList(list, properties)
if #list == 0 then
@@ -207,10 +207,11 @@ function Debug.PrintEntityList(list, properties)
end
local cachedEntities = {}
+
---
---Prints information about all existing entities.
---
----@param properties? string[] # List of property patterns to search for when displaying entity information.
+---@param properties? string[] # List of property patterns to search for
function Debug.PrintAllEntities(properties)
properties = properties or {"GetClassname", "GetName", "GetModelName"}
local list = {}
@@ -226,7 +227,7 @@ end
---
---Prints information about any new entities since the last time `Debug.PrintAllEntities` was called.
---
----@param properties? string[] # List of property patterns to search for when displaying entity information.
+---@param properties? string[] # List of property patterns to search for
function Debug.PrintDiffEntities(properties)
properties = properties or {"GetClassname", "GetName", "GetModelName"}
local list = {}
@@ -241,14 +242,14 @@ function Debug.PrintDiffEntities(properties)
end
---
----Print entities matching a search string.
+---Prints entities matching a search string.
---
---Searches name, classname and model name.
---
----@param search string # Search string, may include `*`.
----@param exact boolean # If the search should match exactly or part of the name.
----@param dont_include_parents boolean # Parents won't be included in the results.
----@param properties? string[] # List of property patterns to search for when displaying entity information.
+---@param search string # Search string, may include `*`
+---@param exact boolean # If the search should match exactly or part of the name
+---@param dont_include_parents boolean # Parents won't be included in the results
+---@param properties? string[] # List of property patterns to search for
function Debug.PrintEntities(search, exact, dont_include_parents, properties)
-- Get all matching ents
local preents = {}
@@ -278,9 +279,9 @@ end
---
---Prints information about all entities within a sphere.
---
----@param origin Vector # Position to search for entities at.
----@param radius number # Max radius to find entities within.
----@param properties? string[] # List of property patterns to search for when displaying entity information.
+---@param origin Vector # Position to search for entities at
+---@param radius number # Max radius to find entities within
+---@param properties? string[] # List of property patterns to search for
function Debug.PrintAllEntitiesInSphere(origin, radius, properties)
Debug.PrintEntityList(Entities:FindAllInSphere(origin, radius), properties)
end
@@ -390,7 +391,7 @@ end
---
---Prints the keys/values of a table but not any tested tables.
---
----@param tbl table # Table to print.
+---@param tbl table # Table to print
function Debug.PrintTableShallow(tbl)
if type(tbl) ~= "table" then return end
print("{")
@@ -403,8 +404,8 @@ end
---
---Prints an ordered table as a numbered list in the console.
---
----@param tbl table # Table to print.
----@param prefix? string # Optional prefix for each line.
+---@param tbl table # Table to print
+---@param prefix? string # Optional prefix for each line
function Debug.PrintList(tbl, prefix)
local m = 0
prefix = prefix or ""
@@ -427,7 +428,7 @@ end
---
---Prints all the values in a table, one value per line, without any numbering or padding.
---
----@param tbl table # Table to print.
+---@param tbl table # Table to print
function Debug.PrintSimpleTable(tbl)
if type(tbl) ~= "table" then
return warn("Parameter 'tbl' is not a table " .. Debug.GetSourceLine(3))
@@ -441,7 +442,7 @@ end
---
---Prints a value and its type in an easy to read format.
---
----@param value any
+---@param value any # Value to print
function Debug.PrintValue(value)
local vs, ts = getValueString(value)
print(vs..ts)
@@ -450,8 +451,8 @@ end
---
---Draws a debug line to an entity in game.
---
----@param ent EntityHandle|string # Handle or targetname of the entity(s) to find.
----@param duration number? # Number of seconds the debug should display for.
+---@param ent EntityHandle|string # Handle or targetname of the entity(s) to find
+---@param duration number? # Number of seconds the debug should display for
---@return EntityHandle[]|EntityHandle? # Entities found, or the entity given
function Debug.ShowEntity(ent, duration)
duration = duration or 20
@@ -503,7 +504,7 @@ CBaseEntity.PrintCriteria = Debug.PrintEntityCriteria
---
---Prints current context criteria for an entity except for values saved using `storage.lua`.
---
----@param ent EntityHandle
+---@param ent EntityHandle # Entity to print
function Debug.PrintEntityBaseCriteria(ent)
---@type table
local c, d = {}, {}
@@ -555,8 +556,8 @@ local classes = {
---
---If the entity is an EntityClass entity the original Valve class name will be returned instead of the EntityClass.
---
----@param ent EntityHandle # The entity to get the class name of.
----@return string # The class name of the entity or "none" if not found.
+---@param ent EntityHandle # The entity to get the class name of
+---@return string # The class name of the entity or "none" if not found
function Debug.GetClassname(ent)
-- AI_Squad doesn't exist until CAI_BaseNPC:GetSquad() is called
@@ -575,6 +576,9 @@ function Debug.GetClassname(ent)
end
end
+---
+---Prints the table values of all built-in Valve classes.
+---
function Debug.PrintMetaClasses()
for val, name in pairs(classes) do
print(name .. ": " .. tostring(val))
@@ -584,6 +588,8 @@ end
---
---Prints a visual ASCII graph showing the distribution of values between a min/max bound.
---
+---Higher heights give more accurate results but can overflow the console making it hard to read.
+---
---E.g.
---
--- Debug.PrintGraph(6, 0, 1, {
@@ -606,10 +612,10 @@ end
--- val1 = 0.5374761223793
--- val2 = 0.7315416932106
---
----@param height integer # Height of the actual graph in print rows. Heigher values give more accurate results but can overflow the console making it hard to read.
----@param min_val? number # Minimum expected value for `name_value_pairs`. Default is `0`.
----@param max_val? number # Maxmimum expected value for `name_value_pairs`. Default is `1`.
----@param name_value_pairs table # Values to visualize on the graph.
+---@param height integer # Height of the actual graph in print rows
+---@param min_val? number # Minimum expected value for `name_value_pairs`; default is `0`
+---@param max_val? number # Maxmimum expected value for `name_value_pairs`; default is `1`
+---@param name_value_pairs table # Values to visualize on the graph
function Debug.PrintGraph(height, min_val, max_val, name_value_pairs)
height = height or 10
min_val = min_val or 0
@@ -735,7 +741,7 @@ end
---
---Prints a nested list of entity inheritance.
---
----@param ent EntityHandle
+---@param ent EntityHandle # Entity to print the inheritance of
function Debug.PrintInheritance(ent)
print(ent:GetClassname() .. " -> " .. tostring(ent))
local parent = getmetatable(ent)
@@ -759,8 +765,8 @@ end
---
---Returns a simplified vector string with decimal places truncated.
---
----@param vector Vector|QAngle|table
----@return string
+---@param vector Vector|QAngle|table # Object to print
+---@return string # Vector string
function Debug.SimpleVector(vector)
return "[" .. math.trunc(vector.x, 3) .. ", " .. math.trunc(vector.y, 3) .. ", " .. math.trunc(vector.z, 3) .. "]"
end
@@ -768,7 +774,7 @@ end
vecstr = Debug.SimpleVector
---
----Draw a simple sphere without worrying about all the properties.
+---Draws a simple sphere without worrying about all the properties.
---
---@param x number # X position
---@param y number # Y position
@@ -801,7 +807,7 @@ function Debug.Sphere(x, y, z, radius, time, color)
end
---
----Draw a simple line without worrying about all the properties.
+---Draws a simple line without worrying about all the properties.
---
---@param startPos Vector # Start position
---@param endPos Vector # End position
@@ -863,7 +869,7 @@ end
---@param tblpart string # Entity table string
---@param colon? string # The colon part
---@param hash? string # The hash part
----@return EntityHandle?
+---@return EntityHandle? # The found entity, or nil if not found
function Debug.FindEntityByHandleString(tblpart, colon, hash)
if tblpart == nil and colon == nil and hash == nil then
devwarn("Must provide a valid entity table string, e.g. 'table: 0x0012b03'")
@@ -919,7 +925,7 @@ end
---Converts a number to its ordinal string representation (e.g., 1 → "1st", 2 → "2nd", 3 → "3rd").
---
---@param n integer # Number to convert to ordinal representation
----@return string
+---@return string # Ordinal string
function Debug.ToOrdinalString(n)
local lastTwo = n % 100
if lastTwo >= 11 and lastTwo <= 13 then
@@ -936,7 +942,7 @@ end
---Get the script name and line number of a function or traceback level.
---
---@param f integer|function # Level or function
----@return string
+---@return string # Source
function Debug.GetSourceLine(f)
return debug.getinfo(f, "S").short_src..":"..tostring(debug.getinfo(f, "l").currentline)
end
@@ -959,8 +965,8 @@ end
---
---Converts a table to single line string representation.
---
----@param tbl table # The table to convert.
----@return string # The string representation.
+---@param tbl table # The table to convert
+---@return string # The string representation
function Debug.TableStr(tbl)
local vals = {}
for key, value in pairs(tbl) do
@@ -975,7 +981,7 @@ end
---
---@param classname string # The classname of the entity
---@param spawnkeys? table|string # The spawnkeys table or targetname
----@return EntityHandle
+---@return EntityHandle # The spawned entity
---@diagnostic disable-next-line: lowercase-global
function entspawn(classname, spawnkeys)
if type(spawnkeys) == "string" then
diff --git a/scripts/vscripts/alyxlib/debug/controller.lua b/scripts/vscripts/alyxlib/debug/controller.lua
index 580ef58..4467dd3 100644
--- a/scripts/vscripts/alyxlib/debug/controller.lua
+++ b/scripts/vscripts/alyxlib/debug/controller.lua
@@ -4,10 +4,11 @@
Allows quick debugging of VR controllers.
- If not using `vscripts/alyxlib/init.lua`, load this file at game start using the following line:
+ If not using `alyxlib/init.lua`, load this file at game start using the following line:
require "alyxlib.debug.controller"
]]
+
-- Used for button descriptions
require "alyxlib.controls.input"
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index 39d4f9b..f1711a4 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -3,6 +3,10 @@
https://github.com/FrostSource/alyxlib
The debug menu allows for easier VR testing by offering a customizable in-game menu.
+
+ If not using `alyxlib/init.lua`, load this file at game start using the following line:
+
+ require "alyxlib.debug.debug_menu"
]]
RegisterAlyxLibCommand("debug_menu_show", function (name, ...)
@@ -49,33 +53,38 @@ DebugMenu.version = "v1.0.0"
---A category of items in the debug menu.
---
---@class DebugMenuCategory
----@field id string # The unique ID for this category.
----@field name string # The display name for this category.
----@field items DebugMenuItem[] # The items in this category.
+---@field id string # The unique ID for this category
+---@field name string # The display name for this category
+---@field items DebugMenuItem[] # The items in this category
---
---An item in the debug menu.
---
---@class DebugMenuItem
----@field categoryId string # The ID of the category this item is in.
----@field id string # The unique ID for this item.
----@field text string # The text to display for this item (if applicable).
----@field callback function # The function to call when this item is clicked.
+---@field categoryId string # The ID of the category this item is in
+---@field id string # The unique ID for this item
+---@field text string # The text to display for this item (if applicable)
+---@field callback function # The function to call when this item is clicked
---@field type "button"|"toggle"|"separator"|"slider"|"cycle" # Type of menu element this item is.
----@field default any|function # The default value sent to the menu. If this is a function the return value will be used.
----@field min number # Minimum value of this slider.
----@field max number # Maxmimum value of this slider.
----@field isPercentage boolean # If true, this slider displays its value as a percentage of min/max.
----@field convar string # The console variable associated with this element.
----@field values {text:string,value:any}[] # Text/value pairs for this cycler.
----@field truncate number # The number of decimal places to truncate the slider value to (-1 for no truncating).
----@field increment number # The increment value to snap the slider value to (0 for no snapping).
----@field condition? string|fun():boolean # The condition that must be met for this item to be visible.
+---@field default any|function # The default value sent to the menu. If this is a function the return value will be used
+---@field min number # Minimum value of this slider
+---@field max number # Maxmimum value of this slider
+---@field isPercentage boolean # If true, this slider displays its value as a percentage of min/max
+---@field convar string # The console variable associated with this element
+---@field values {text:string,value:any}[] # Text/value pairs for this cycler
+---@field truncate number # The number of decimal places to truncate the slider value to (-1 for no truncating)
+---@field increment number # The increment value to snap the slider value to (0 for no snapping)
+---@field condition? string|fun():boolean # The condition that must be met for this item to be visible
+---
---The panel entity.
+---
---@type CPointClientUIWorldPanel
DebugMenu.panel = nil
+---
+---The categories in the debug menu.
+---
---@type DebugMenuCategory[]
DebugMenu.categories = {}
@@ -488,7 +497,7 @@ function DebugMenu:ClickHoveredButton()
end
---
----Get a debug menu item by id.
+---Gets a debug menu item by id.
---
---@param id string # The item ID
---@param categoryId? string # Optionally specify a category to look in. If not specified, will look in all categories
@@ -506,7 +515,7 @@ function DebugMenu:GetItem(id, categoryId)
end
---
----Get a debug menu category by id.
+---Gets a debug menu category by id.
---
---@param id string # The category ID
---@return DebugMenuCategory? # The category if it exists
@@ -520,7 +529,7 @@ function DebugMenu:GetCategory(id)
end
---
----Add a category to the debug menu.
+---Adds a category to the debug menu.
---
---@param id string # The unique ID for this category
---@param name string # The display name for this category
@@ -538,7 +547,7 @@ function DebugMenu:AddCategory(id, name)
end
---
----Add a separator line to a category.
+---Adds a separator line to a category.
---
---@param categoryId string # The category ID to add the separator to
---@param separatorId? string # Optional ID for the separator if you want to modify it later
@@ -559,7 +568,7 @@ function DebugMenu:AddSeparator(categoryId, separatorId, text)
end
---
----Add a button to a category.
+---Adds a button to a category.
---
---@param categoryId string # The category ID to add the button to
---@param buttonId string # The unique ID for this button
@@ -591,7 +600,7 @@ function DebugMenu:AddButton(categoryId, buttonId, text, command)
end
---
----Add a toggle to a category.
+---Adds a toggle to a category.
---
---@param categoryId string # The category ID to add the toggle to
---@param toggleId string # The unique ID for this toggle
@@ -624,7 +633,7 @@ function DebugMenu:AddToggle(categoryId, toggleId, text, convar, callback, start
end
---
----Add a center aligned label to a category.
+---Adds a center aligned label to a category.
---
---@param categoryId string # The category ID to add the label to
---@param labelId string # The unique ID for this label
@@ -645,7 +654,7 @@ function DebugMenu:AddLabel(categoryId, labelId, text)
end
---
----Add value slider to a category.
+---Adds value slider to a category.
---
---@param categoryId string # The ID of the category to add this slider to
---@param sliderId string # A unique ID for this slider
@@ -690,7 +699,7 @@ function DebugMenu:AddSlider(categoryId, sliderId, text, convar, min, max, isPer
end
---
----Add a value cycler to a category.
+---Adds a value cycler to a category.
---
---Cyclers allow users to choose from a set of values.
---
@@ -747,7 +756,7 @@ function DebugMenu:AddCycle(categoryId, cycleId, title, convar, values, callback
end
---
----Set the text of an item.
+---Sets the text of an item.
---
---Only works on the following types:
--- - button
@@ -772,13 +781,13 @@ function DebugMenu:SetItemText(categoryId, itemId, text)
end
---
----Sets the index of a category in the debug menu.
+---Sets the index of a category in the debug menu.
---Categories are ordered by their index, starting from 1.
---
---This is an advanced function and should be used with caution.
---
----@param categoryId string # Id of the category to change.
----@param index number # New index for the category.
+---@param categoryId string # Id of the category to change
+---@param index number # New index for the category
function DebugMenu:SetCategoryIndex(categoryId, index)
local category, currentIndex = self:GetCategory(categoryId)
if not category then
diff --git a/scripts/vscripts/alyxlib/debug/novr.lua b/scripts/vscripts/alyxlib/debug/novr.lua
index 039fdc0..abc1b9f 100644
--- a/scripts/vscripts/alyxlib/debug/novr.lua
+++ b/scripts/vscripts/alyxlib/debug/novr.lua
@@ -4,13 +4,16 @@
Adds functions and console commands to help debugging outside VR mode.
- If not using `vscripts/alyxlib/init.lua`, load this file at game start using the following line:
+ If not using `alyxlib/init.lua`, load this file at game start using the following line:
require "alyxlib.debug.novr"
]]
local version = "v1.1.0"
+---
+---Provides functions to help debugging outside VR mode.
+---
---@class NoVR
NoVR = {}
@@ -19,6 +22,9 @@ NoVR.AutoStartInToolsMode = false
--#region Interactions
+---
+---NoVR interaction data.
+---
---@class NoVrInteractClass
---@field class string
---@field hold? boolean
@@ -77,7 +83,7 @@ local interactClasses =
}
---
----Add an interaction class for the NoVR player to interact with.
+---Adds an interaction class for the NoVR player to interact with.
---
---@param title string # Text to show in-game on the entity
---@param class string # Class to interact with
@@ -417,7 +423,7 @@ end, "", 0)
local novrBindings = {}
---
----Unbind all keys bound by [NoVR:BindKey](lua://NoVR.BindKey)
+---Unbinds all keys bound by [NoVR:BindKey](lua://NoVR.BindKey)
---
function NoVR:UnbindKeys()
-- for _, binding in ipairs(novrBindings) do
@@ -429,9 +435,9 @@ function NoVR:UnbindKeys()
end
---
----Bind a keyboard key to a callback function.
+---Binds a keyboard key to a callback function.
---
----@param key KeyboardKey
+---@param key KeyboardKey # Key to bind
---@param callback fun()|string # Callback function or command string
---@param name? string # Optional name for the callback command
function NoVR:BindKey(key, callback, name)
diff --git a/scripts/vscripts/alyxlib/debug/profiling.lua b/scripts/vscripts/alyxlib/debug/profiling.lua
index 146b22a..e2b1be7 100644
--- a/scripts/vscripts/alyxlib/debug/profiling.lua
+++ b/scripts/vscripts/alyxlib/debug/profiling.lua
@@ -23,7 +23,7 @@ local freq = ffi.new("LARGE_INTEGER")
kernal32.QueryPerformanceFrequency(freq)
---
----Profiler class
+---Profiler class.
---
---@class Profiler
local profiler = {
@@ -108,17 +108,17 @@ function profiler:Profile(func)
end
---
----Get the mean (average) profiled time in seconds.
+---Gets the mean (average) profiled time in seconds.
---
----@return number
+---@return number # The mean profiled time
function profiler:GetMean()
return self.totalTime / self.numMeasurements
end
---
----Get the median profiled time in seconds.
+---Gets the median profiled time in seconds.
---
----@return number
+---@return number # The median profiled time
function profiler:GetMedian()
if self.numMeasurements == 0 then
return 0
@@ -136,14 +136,14 @@ function profiler:GetMedian()
end
---
----Calculate the standard deviation of the measurements.
+---Calculates the standard deviation of the measurements.
---
----The standard deviation is used to determine how spread out the measurements are.
----The higher the value, the more the measurements deviate from the mean, indicating greater variability.
----A lower value means the measurements are closer to the mean, indicating more consistency.
+---The standard deviation is used to determine how spread out the measurements are.
+---The higher the value, the more the measurements deviate from the mean, indicating greater variability.
+---A lower value means the measurements are closer to the mean, indicating more consistency.
---A result of 0 means there is no variability (either because all measurements are the same, or there are no measurements).
---
----@return number
+---@return number # The standard deviation
function profiler:GetStandardDeviation()
local mean = self:GetMean()
if self.useRunningTotal then
diff --git a/scripts/vscripts/alyxlib/debug/vr.lua b/scripts/vscripts/alyxlib/debug/vr.lua
index f01fbb2..acb33cb 100644
--- a/scripts/vscripts/alyxlib/debug/vr.lua
+++ b/scripts/vscripts/alyxlib/debug/vr.lua
@@ -4,7 +4,7 @@
Adds console commands to help debugging VR specific features.
- If not using `vscripts/alyxlib/init.lua`, load this file at game start using the following line:
+ If not using `alyxlib/init.lua`, load this file at game start using the following line:
require "alyxlib.debug.vr"
]]
diff --git a/scripts/vscripts/alyxlib/extensions/entities.lua b/scripts/vscripts/alyxlib/extensions/entities.lua
index 3244da2..8375ece 100644
--- a/scripts/vscripts/alyxlib/extensions/entities.lua
+++ b/scripts/vscripts/alyxlib/extensions/entities.lua
@@ -4,7 +4,7 @@
Extensions for the `Entities` class.
- If not using `vscripts/alyxlib/init.lua`, load this file at game start using the following line:
+ If not using `alyxlib/init.lua`, load this file at game start using the following line:
require "alyxlib.extensions.entities"
]]
@@ -14,7 +14,7 @@ local version = "v1.8.0"
---
----Finds the entity in a given list that is closest to a specified position.
+---Finds the entity in a given list that is closest to a specified position.
---Uses `CalcClosestPointOnEntityOBB` to determine the nearest point on each entity's bounding box.
---
--- @param ents EntityHandle[] # A list of entities to check
@@ -49,7 +49,7 @@ end
---
---If no name is provided, the function falls back to finding the nearest entity of the given classname.
---
----@param name string # The unique name of the entity (if available, "" if not)
+---@param name string|"" # The unique name of the entity (if available, "" if not)
---@param class string # The classname of the entity (fallback if name isn't available)
---@param position Vector # The position to search around
---@param radius? number # The max search radius (default: 128)
@@ -76,7 +76,7 @@ end
---
---Gets an array of every entity that currently exists.
---
----@return EntityHandle[]
+---@return EntityHandle[] # List of all entities
function Entities:All()
local ents = {}
local e = Entities:First()
@@ -90,21 +90,21 @@ end
---
---Gets a random entity in the map.
---
----@return EntityHandle
+---@return EntityHandle # A random entity
function Entities:Random()
local all = Entities:All()
return all[RandomInt(1, #all)]
end
---
----Find an entity within the same prefab as another entity.
+---Finds a named entity within the same name-fixed-up prefab as another entity.
---
----Will have issues in nested prefabs.
+---Might have issues in nested prefabs.
---
----@param entity EntityHandle
----@param name string
----@return EntityHandle? # The found entity, nil if not found.
----@return string # Prefab part of the name.
+---@param entity EntityHandle # A known entity in the prefab
+---@param name string # The name to search for
+---@return EntityHandle? # The found entity, or `nil` if not found
+---@return string # Prefab part of the name
function Entities:FindInPrefab(entity, name)
local myname = entity:GetName()
for _,ent in ipairs(Entities:FindAllByName('*' .. name)) do
@@ -117,26 +117,26 @@ function Entities:FindInPrefab(entity, name)
end
---
----Find an entity within the same prefab as this entity.
+---Finds a named entity within the same name-fixed-up prefab as this entity.
---
----Will have issues in nested prefabs.
+---Might have issues in nested prefabs.
---
----@param name string
----@return EntityHandle?
----@return string # Prefab part of the name.
+---@param name string # The name to search for
+---@return EntityHandle? # The found entity, or `nil` if not found
+---@return string # Prefab part of the name
function CEntityInstance:FindInPrefab(name)
return Entities:FindInPrefab(self, name)
end
---
----Find all entities with a cone.
+---Finds all entities with a cone.
---
----@param origin Vector # Origin of the cone in worldspace.
----@param direction Vector # Normalized direction vector.
----@param maxDistance number # Max distance the cone will extend towards `direction`.
----@param maxAngle number # Field-of-view in degrees that the cone can see, [0-180].
----@param checkEntityBounds boolean # If true the entity bounding box will be tested as well as the origin.
----@return EntityHandle[] # List of entities found within the cone.
+---@param origin Vector # Origin of the cone in worldspace
+---@param direction Vector # Normalized direction vector
+---@param maxDistance number # Max distance the cone will extend towards `direction`
+---@param maxAngle number # Field-of-view in degrees that the cone can see, [0-180]
+---@param checkEntityBounds boolean # If true the entity bounding box will be tested as well as the origin
+---@return EntityHandle[] # List of entities found within the cone
function Entities:FindAllInCone(origin, direction, maxDistance, maxAngle, checkEntityBounds)
local cosMaxAngle = math.cos(math.rad(maxAngle))
local entitiesInSphere = Entities:FindAllInSphere(origin, maxDistance)
@@ -168,12 +168,12 @@ function Entities:FindAllInCone(origin, direction, maxDistance, maxAngle, checkE
end
---
----Find all entities within `mins` and `maxs` bounding box.
+---Finds all entities within `mins` and `maxs` bounding box.
---
----@param mins Vector # Mins vector in world-space.
----@param maxs Vector # Maxs vector in world-space.
----@param checkEntityBounds? boolean # If true the entity bounding boxes will be used for the check instead of the origin.
----@return EntityHandle[] # List of entities found.
+---@param mins Vector # Mins vector in world-space
+---@param maxs Vector # Maxs vector in world-space
+---@param checkEntityBounds? boolean # `true` if the bounding boxes should be used for the check instead of their origin
+---@return EntityHandle[] # List of entities found
function Entities:FindAllInBounds(mins, maxs, checkEntityBounds)
local center = (mins + maxs) / 2
local maxRadius = (maxs - center):Length()
@@ -194,12 +194,12 @@ function Entities:FindAllInBounds(mins, maxs, checkEntityBounds)
end
---
----Find all entities within an `origin` centered box.
+---Finds all entities within an `origin` centered box.
---
----@param width number # Size of the box on the X axis.
----@param length number # Size of the box on the Y axis.
----@param height number # Size of the box on the Z axis.
----@return EntityHandle[] # List of entities found.
+---@param width number # Size of the box on the X axis
+---@param length number # Size of the box on the Y axis
+---@param height number # Size of the box on the Z axis
+---@return EntityHandle[] # List of entities found
function Entities:FindAllInBox(origin, width, length, height)
return Entities:FindAllInBounds(
Vector(origin - width, origin - length, origin - height),
@@ -208,21 +208,21 @@ function Entities:FindAllInBox(origin, width, length, height)
end
---
----Find all entities within an `origin` centered cube of a given `size.`
+---Finds all entities within an `origin` centered cube of a given `size.`
---
----@param origin Vector # World space cube position.
----@param size number # Size of the cube in all directions.
----@return EntityHandle[] # List of entities found.
+---@param origin Vector # World space cube position
+---@param size number # Size of the cube in all directions
+---@return EntityHandle[] # List of entities found
function Entities:FindAllInCube(origin, size)
return Entities:FindAllInBox(origin, size, size, size)
end
---
----Find the nearest entity to a world position.
+---Finds the nearest entity to a world position.
---
----@param origin Vector # Position to check from.
----@param maxRadius number # Maximum radius to check from `origin`.
----@return EntityHandle? # The nearest entity found, or nil if none found.
+---@param origin Vector # Position to check from
+---@param maxRadius number # Maximum radius to check from `origin`
+---@return EntityHandle? # The nearest entity found, or nil if none found
function Entities:FindNearest(origin, maxRadius)
local nearestEnt = nil
local nearestDistanceSq = math.huge
@@ -242,8 +242,8 @@ end
---
---Finds all entities in the map from a list of classnames.
---
----@param classes string[]
----@return EntityHandle[]
+---@param classes string[] # List of classnames
+---@return EntityHandle[] # List of entities
function Entities:FindAllByClassnameList(classes)
local ents = {}
for _, class in ipairs(classes) do
@@ -255,10 +255,10 @@ end
---
---Finds all entities within a radius from a list of classnames.
---
----@param classes string[]
----@param origin Vector
----@param maxRadius number
----@return EntityHandle[]
+---@param classes string[] # List of classnames
+---@param origin Vector # Position to check from
+---@param maxRadius number # Maximum radius to check from `origin`
+---@return EntityHandle[] # List of entities
function Entities:FindAllByClassnameListWithin(classes, origin, maxRadius)
local ents = {}
for _, class in ipairs(classes) do
@@ -270,10 +270,10 @@ end
---
---Find the entity from a list of possible classnames which is closest to a world position.
---
----@param classes string[]
----@param origin Vector
----@param maxRadius number
----@return EntityHandle?
+---@param classes string[] # List of classnames
+---@param origin Vector # Position to check from
+---@param maxRadius number # Maximum radius to check from `origin`
+---@return EntityHandle? # The nearest entity found, or `nil` if none found
function Entities:FindByClassnameListNearest(classes, origin, maxRadius)
local nearestEnt = nil
local nearestDistanceSq = math.huge
@@ -295,7 +295,7 @@ end
---
---Finds all NPCs within the map.
---
----@return CAI_BaseNPC[]
+---@return CAI_BaseNPC[] # List of NPC entities
function Entities:FindAllNPCs()
local npcs = {}
local ent = Entities:First()
@@ -332,12 +332,12 @@ function Entities:IterateAllNPCs()
end
---
----Find all entities by model name within a radius.
+---Finds all entities by model name within a radius.
---
----@param modelName string
----@param origin Vector
----@param maxRadius number
----@return EntityHandle[]
+---@param modelName string # Model name
+---@param origin Vector # Position to check from
+---@param maxRadius number # Maximum radius to check from `origin`
+---@return EntityHandle[] # List of entities
function Entities:FindAllByModelWithin(modelName, origin, maxRadius)
local ents = {}
local currentEnt = Entities:FindByModelWithin(nil, modelName, origin, maxRadius)
@@ -349,12 +349,12 @@ function Entities:FindAllByModelWithin(modelName, origin, maxRadius)
end
---
----Find the entity by model name nearest to a point.
+---Finds the entity by model name nearest to a point.
---
----@param modelName string
----@param origin Vector
----@param maxRadius number
----@return EntityHandle?
+---@param modelName string # Model name
+---@param origin Vector # Position to check from
+---@param maxRadius number # Maximum radius to check from `origin`
+---@return EntityHandle? # The nearest entity found, or `nil` if none found
function Entities:FindByModelNearest(modelName, origin, maxRadius)
local closestEnt = nil
local closestDist = math.huge
@@ -371,12 +371,12 @@ function Entities:FindByModelNearest(modelName, origin, maxRadius)
end
---
----Find the first entity whose model name contains `namePattern`.
+---Finds the first entity whose model name contains `namePattern`.
---
---This works by searching every entity in the map and may incur a performance hit in large maps if used often.
---
----@param namePattern string
----@return EntityHandle?
+---@param namePattern string # Pattern to search for
+---@return EntityHandle? # The found entity, or `nil` if not found
function Entities:FindByModelPattern(namePattern)
local ent = Entities:First()
while ent ~= nil do
@@ -388,12 +388,12 @@ function Entities:FindByModelPattern(namePattern)
end
---
----Find all entities whose model name contains `namePattern`.
+---Finds all entities whose model name contains `namePattern`.
---
---This works by searching every entity in the map and may incur a performance hit in large maps if used often.
---
----@param namePattern string
----@return EntityHandle[]
+---@param namePattern string # Pattern to search for
+---@return EntityHandle[] # List of entities
function Entities:FindAllByModelPattern(namePattern)
local ents = {}
local ent = Entities:First()
diff --git a/scripts/vscripts/alyxlib/extensions/entity.lua b/scripts/vscripts/alyxlib/extensions/entity.lua
index fff7b82..e04750a 100644
--- a/scripts/vscripts/alyxlib/extensions/entity.lua
+++ b/scripts/vscripts/alyxlib/extensions/entity.lua
@@ -4,7 +4,7 @@
Provides base entity extension methods.
- If not using `vscripts/alyxlib/init.lua`, load this file at game start using the following line:
+ If not using `alyxlib/init.lua`, load this file at game start using the following line:
require "alyxlib.extensions.entity"
]]
@@ -12,9 +12,9 @@
local version = "v2.7.1"
---
----Get the entities parented to this entity. Including children of children.
+---Gets the entities parented to this entity. Including children of children.
---
----This is a memory safe version of GetChildren() which causes a memory leak when called.
+---This is a memory safe version of [GetChildren](lua://CBaseEntity.GetChildren) which causes a memory leak when called.
---If you need to get children often you should use this function instead.
---
---@return EntityHandle[]
@@ -27,14 +27,14 @@ function CBaseEntity:GetChildrenMemSafe()
end
---
----Returns a `function` that iterates over all children of this entity.
+---Returns a `function` that iterates over all children of this entity.
---The `function` returns the next child every time it is called until no more children exist,
---in which case `nil` is returned.
---
---Useful in `for` loops:
---
--- for child in thisEntity:IterateChildren() do
---- print(Debug.EntStr(child))
+--- print(entstr(child))
--- end
---
---This function is memory safe.
@@ -60,14 +60,14 @@ function CBaseEntity:IterateChildren()
end
---
----Returns a `function` that iterates over all children of this entity in **breadth-first order**.
+---Returns a `function` that iterates over all children of this entity in **breadth-first order**.
---The `function` returns the next child every time it is called until no more children exist,
---in which case `nil` is returned.
---
---Useful in `for` loops:
---
--- for child in thisEntity:IterateChildrenBreadthFirst() do
---- print(Debug.EntStr(child))
+--- print(entstr(child))
--- end
---
---Unlike [IterateChildren](lua://CBaseEntity.IterateChildren), this visits all immediate children first,
@@ -104,7 +104,7 @@ end
---
----Get the top level entities parented to this entity. Not children of children.
+---Gets the top level entities parented to this entity. *Not* children of children.
---
---This function is memory safe.
---
@@ -122,6 +122,8 @@ end
---
---Get the first child in the hierarchy that has targetname or classname.
---
+---This function is memory safe.
+---
---@param name string # The name or classname to look for, supports wildcard '*'
---@return EntityHandle?
function CBaseEntity:GetChild(name)
@@ -166,25 +168,25 @@ end
---
----Send an input to this entity.
+---Sends an input to this entity.
---
----@param action string # Input name.
----@param value? any # Parameter override for the input.
----@param delay? number # Delay in seconds.
----@param activator? EntityHandle
----@param caller? EntityHandle
+---@param action string # Input name
+---@param value? any # Parameter override for the input
+---@param delay? number # Delay in seconds
+---@param activator? EntityHandle # IO activator
+---@param caller? EntityHandle # IO caller
function CBaseEntity:EntFire(action, value, delay, activator, caller)
DoEntFireByInstanceHandle(self, action, value and tostring(value) or "", delay or 0, activator or nil, caller or nil)
end
---
----Get the first child in this entity's hierarchy with a given `classname`.
+---Gets the first child in this entity's hierarchy with a given `classname`.
---Searches using **breadth-first traversal**, so it finds the closest matching child first.
---
---This function is memory safe.
---
----@param classname string # Classname to search for.
----@return EntityHandle? # The first matching child found, or `nil` if none exists.
+---@param classname string # Classname to search for
+---@return EntityHandle? # The first matching child found, or `nil` if none exists
function CBaseEntity:GetFirstChildWithClassname(classname)
for child in self:IterateChildrenBreadthFirst() do
if child:GetClassname() == classname then
@@ -196,13 +198,13 @@ function CBaseEntity:GetFirstChildWithClassname(classname)
end
---
----Get the first child in this entity's hierarchy with a given `name`.
+---Get the first child in this entity's hierarchy with a given `name`.
---Searches using **breadth-first traversal**, so it finds the closest matching child first.
---
---This function is memory safe.
---
----@param name string # Targetname to search for.
----@return EntityHandle? # The first matching child found, or `nil` if none exists.
+---@param name string # Targetname to search for
+---@return EntityHandle? # The first matching child found, or `nil` if none exists
function CBaseEntity:GetFirstChildWithName(name)
for child in self:IterateChildrenBreadthFirst() do
if child:GetName() == name then
@@ -214,34 +216,34 @@ function CBaseEntity:GetFirstChildWithName(name)
end
---
----Set entity pitch, yaw, roll from a `QAngle`.
+---Sets entity pitch, yaw, roll from a `QAngle`.
---
----@param qangle QAngle # The rotation to set (pitch, yaw, roll).
+---@param qangle QAngle # The QAngle rotation to set
function CBaseEntity:SetQAngle(qangle)
self:SetAngles(qangle.x, qangle.y, qangle.z)
end
---
----Set entity local pitch, yaw, roll from a `QAngle`.
+---Sets entity local pitch, yaw, roll from a `QAngle`.
---
----@param qangle QAngle # The rotation to set (pitch, yaw, roll).
+---@param qangle QAngle # The QAngle rotation to set
function CBaseEntity:SetLocalQAngle(qangle)
self:SetLocalAngles(qangle.x, qangle.y, qangle.z)
end
---
----Set entity pitch, yaw or roll. Supply `nil` for any parameter to leave it unchanged.
+---Sets entity pitch, yaw or roll. Supply `nil` for any parameter to leave it unchanged.
---
----@param pitch? number # Pitch angle, or nil to leave unchanged.
----@param yaw? number # Pitch angle, or nil to leave unchanged.
----@param roll? number # Pitch angle, or nil to leave unchanged.
+---@param pitch? number # Pitch angle
+---@param yaw? number # Yaw angle
+---@param roll? number # Roll angle
function CBaseEntity:SetAngle(pitch, yaw, roll)
local angles = self:GetAngles()
self:SetAngles(pitch or angles.x, yaw or angles.y, roll or angles.z)
end
---
----Resets local origin and angle to [0,0,0].
+---Resets local origin and angles to (0, 0, 0).
---
function CBaseEntity:ResetLocal()
self:SetLocalOrigin(Vector())
@@ -249,46 +251,46 @@ function CBaseEntity:ResetLocal()
end
---
----Get the bounding size of the entity.
+---Gets the bounding size of the entity.
---
----@return Vector # Bounding size of the entity as a Vector.
+---@return Vector # Bounding size of the entity as a Vector
function CBaseEntity:GetSize()
return self:GetBoundingMaxs() - self:GetBoundingMins()
end
---
----Get the biggest bounding box axis of the entity.
----This will be `size.x`, `size.y` or `size.z`.
+---Gets the biggest bounding box axis of the entity.
+---This will be either `size.x`, `size.y` or `size.z`.
---
----@return number # The largest bounding value.
+---@return number # The largest bounding value
function CBaseEntity:GetBiggestBounding()
local size = self:GetSize()
return math.max(size.x, size.y, size.z)
end
---
----Get the radius of the entity's bounding box.
+---Gets the radius of the entity's bounding box.
---This is half the size of the bounding box along its largest axis.
---
----@return number # The bounding radius value.
+---@return number # The bounding radius value
function CBaseEntity:GetRadius()
return self:GetSize():Length() * 0.5
end
---
----Get the volume of the entity bounds in cubic inches.
+---Gets the volume of the entity bounds in cubic inches.
---
----@return number # The volume of the entity bounds.
+---@return number # The volume of the entity bounds
function CBaseEntity:GetVolume()
local size = self:GetSize() * self:GetAbsScale()
return size.x * size.y * size.z
end
---
----Get each corner of the entity's bounding box.
+---Gets each corner of the entity's bounding box.
---
----@param rotated? boolean # If true, corners are rotated by the entity's angles.
----@return Vector[] # List of 8 corner positions.
+---@param rotated? boolean # If `true`, corners are rotated by the entity's angles
+---@return Vector[] # List of 8 corner positions
function CBaseEntity:GetBoundingCorners(rotated)
local bounds = self:GetBounds()
local origin = self:GetOrigin()
@@ -314,12 +316,12 @@ function CBaseEntity:GetBoundingCorners(rotated)
end
---
----Check if entity is within the given worldspace bounds.
+---Checks if entity is within the given worldspace bounds.
---
----@param mins Vector # Worldspace minimum vector for the bounds.
----@param maxs Vector # Worldspace minimum vector for the bounds.
----@param checkEntityBounds? boolean # If true the entity bounding box will be used for the check instead of its origin.
----@return boolean # True if the entity is within the bounds, false otherwise.
+---@param mins Vector # Worldspace minimum vector for the bounds
+---@param maxs Vector # Worldspace maximum vector for the bounds
+---@param checkEntityBounds? boolean # If `true` the entity bounding box will be used for the check instead of its origin
+---@return boolean # `true` if the entity is within the bounds, `false` otherwise.
function CBaseEntity:IsWithinBounds(mins, maxs, checkEntityBounds)
local selfMins, selfMaxs
if checkEntityBounds then
@@ -339,32 +341,34 @@ function CBaseEntity:IsWithinBounds(mins, maxs, checkEntityBounds)
end
---
----Send the `DisablePickup` input to the entity.
+---Sends the `DisablePickup` input to the entity.
---
function CBaseEntity:DisablePickup()
DoEntFireByInstanceHandle(self, "DisablePickup", "", 0, self, self)
end
---
----Send the `EnablePickup` input to the entity.
+---Sends the `EnablePickup` input to the entity.
---
function CBaseEntity:EnablePickup()
DoEntFireByInstanceHandle(self, "EnablePickup", "", 0, self, self)
end
---
----Delay some code using this entity.
+---Delays some code, using this entity as the context.
---
----@param func fun() # The function to delay.
----@param delay? number # Optional delay in seconds (default 0).
+---@param func fun() # The function to delay
+---@param delay? number # Delay in seconds (default 0)
function CBaseEntity:Delay(func, delay)
self:SetContextThink(DoUniqueString("delay"), function() func() end, delay or 0)
end
---
----Get all parents in the hierarchy upwards.
+---Gets all parents in the hierarchy upwards, from immediate parent up to the root.
---
----@return EntityHandle[] # List of parent entities, from immediate parent up to the root.
+---@see CBaseEntity.GetMoveParent # The immediate parent
+---@see CBaseEntity.GetRootMoveParent # The root parent
+---@return EntityHandle[] # List of parent entities
function CBaseEntity:GetParents()
local parents = {}
local parent = self:GetMoveParent()
@@ -376,9 +380,9 @@ function CBaseEntity:GetParents()
end
---
----Set if the prop is allowed to be dropped. Only works for physics based props.
+---Sets if the prop is not allowed to be dropped. Only works for physics based props.
---
----@param enabled boolean # True if the prop can't be dropped, false for can be dropped.
+---@param enabled boolean # `true` if the prop is not allowed to be dropped
function CBaseEntity:DoNotDrop(enabled)
if enabled then
self:Attribute_SetIntValue("DoNotDrop", 1)
@@ -388,20 +392,21 @@ function CBaseEntity:DoNotDrop(enabled)
end
---
----Get all criteria on this entity as a table.
+---Gets all criteria for this entity as a table.
---
----@return CriteriaTable # A table of criteria key-value pairs.
+---@return CriteriaTable # Criteria key-value pairs
function CBaseEntity:GetCriteria()
local c = {}
self:GatherCriteria(c)
return c
end
+
---
----Get all entities owned by this entity.
+---Gets all entities owned by this entity.
---
---**Note:** This searches all entities in the map and should be used sparingly.
---
----@return EntityHandle[] # List of owned entities.
+---@return EntityHandle[] # List of owned entities
function CBaseEntity:GetOwnedEntities()
local ents = {}
local ent = Entities:First()
@@ -415,9 +420,9 @@ function CBaseEntity:GetOwnedEntities()
end
---
----Set the alpha modulation of this entity, plus any children that support [SetRenderAlpha](lua://CBaseModelEntity.SetRenderAlpha).
+---Sets the alpha modulation of this entity, plus any children that support [SetRenderAlpha](lua://CBaseModelEntity.SetRenderAlpha).
---
----@param alpha integer # Alpha value (0 = fully transparent, 255 = fully opaque).
+---@param alpha integer # Alpha value (0 = fully transparent, 255 = fully opaque)
function CBaseModelEntity:SetRenderAlphaAll(alpha)
for _, child in ipairs(self:GetChildren()) do
if child.SetRenderAlpha then
@@ -430,7 +435,7 @@ end
---
---Moves the entity so that its center is at the given position.
---
----@param position Vector # The new center position.
+---@param position Vector # The new center position
function CBaseEntity:SetCenter(position)
local center = self:GetCenter()
local origin = self:GetOrigin()
@@ -443,10 +448,10 @@ end
---
----Set the entity's origin so that the specified attachment point aligns with the given world position.
+---Sets the entity's origin so that the specified attachment point aligns with the given world position.
---
----@param position Vector # The target world position for the attachment point.
----@param attachment string # The name of the attachment point to align.
+---@param position Vector # The target world position for the attachment point
+---@param attachment string # The name of the attachment point to align
function CBaseAnimating:SetOriginByAttachment(position, attachment)
local offset = self:GetAttachmentOrigin(self:ScriptLookupAttachment(attachment))
local origin = self:GetOrigin()
@@ -457,7 +462,7 @@ function CBaseAnimating:SetOriginByAttachment(position, attachment)
end
---
----Track a property function using a callback when a change is detected.
+---Tracks a property function and calls a callback when a change is detected.
---
--- -- Make entity fully opaque if alpha is ever detected below 255
--- thisEntity:TrackProperty(thisEntity.GetRenderAlpha, function(prevValue, newValue)
@@ -488,19 +493,19 @@ function CBaseEntity:TrackProperty(propertyFunction, onChangeFunction, interval,
end
---
----Untrack a property function which was set to be tracked using `CBaseEntity:TrackProperty`.
+---Untracks a property function which was set to be tracked using [TrackProperty](lua://CBaseEntity.TrackProperty).
---
----@param propertyFunction fun(handle: EntityHandle): any # Property function to untrack, e.g. GetRenderAlpha.
+---@param propertyFunction fun(handle: EntityHandle): any # Property function to untrack
function CBaseEntity:UntrackProperty(propertyFunction)
self:SetContextThink(tostring(propertyFunction), nil, 0)
end
---
----Quickly start a think function on the entity with a random name and no delay.
+---Quickly starts a think function on the entity with a random name.
---
----@param func fun():number? # The think function.
----@param delay? number # Delay before starting the think.
----@return string # The name of the think for stopping later if desired.
+---@param func fun():number? # The think function
+---@param delay? number # Delay before starting the think (default: 0)
+---@return string # The name of the think for stopping later if desired
function CBaseEntity:QuickThink(func, delay)
local name = DoUniqueString("QuickThink")
self:SetContextThink(name, func, delay or 0)
@@ -510,7 +515,7 @@ end
---
---Sets whether the entity is rendered or not.
---
----@param renderingEnabled boolean # If false the entity will become invisible.
+---@param renderingEnabled boolean # `true` to enable rendering, `false` to disable
function CBaseEntity:SetRenderingEnabled(renderingEnabled)
if renderingEnabled then
self:RemoveEffects(0x020)
@@ -522,7 +527,7 @@ end
---
---Sets whether the entity casts a shadow or not.
---
----@param shadowEnabled boolean # If false the entity will not cast a dynamic shadow.
+---@param shadowEnabled boolean # `true` to enable shadow casting, `false` to disable
function CBaseEntity:SetCastShadow(shadowEnabled)
if shadowEnabled then
self:RemoveEffects(0x010)
@@ -533,11 +538,13 @@ end
---
---Adds an I/O connection that will call the function on the passed entity when the specified output fires.
----This means the redirection is persistent after game loads.
---
----@param output string # The name of the output to redirect.
----@param func function # The function to redirect to.
----@param entity? EntityHandle # The entity to redirect to, defaults to the calling entity.
+---This is persistent after game loads.
+---
+---@see CEntityInstance.DisconnectRedirectedOutput # for disconnecting
+---@param output string # The name of the output to redirect
+---@param func function # The function to redirect to
+---@param entity? EntityHandle # The entity to redirect to, defaults to the calling entity
function CEntityInstance:RedirectOutputFunc(output, func, entity)
entity = entity or self
@@ -551,8 +558,8 @@ end
---
---Gets the position in front of the entity’s eyes at the specified distance.
---
----@param distance number # How far in front of the eyes to get the position.
----@return Vector # The world position in front of the eyes.
+---@param distance number # How far in front of the eyes
+---@return Vector # The world position in front of the eyes
function CBaseEntity:DistanceFromEyes(distance)
return self:EyePosition() + self:EyeAngles():Forward() * distance
end
@@ -560,8 +567,8 @@ end
---
---Gets the world origin position of a named attachment point.
---
----@param name string # The name of the attachment.
----@return Vector # The world position of the attachment.
+---@param name string # The name of the attachment
+---@return Vector # The world position of the attachment
function CBaseAnimating:GetAttachmentNameOrigin(name)
return self:GetAttachmentOrigin(self:ScriptLookupAttachment(name))
end
@@ -569,8 +576,8 @@ end
---
---Gets the world angles (rotation) of a named attachment point.
---
----@param name string # The name of the attachment.
----@return Vector # The world rotation angles of the attachment.
+---@param name string # The name of the attachment
+---@return Vector # The world rotation angles of the attachment
function CBaseAnimating:GetAttachmentNameAngles(name)
return self:GetAttachmentAngles(self:ScriptLookupAttachment(name))
end
@@ -578,8 +585,8 @@ end
---
---Gets the forward direction vector of a named attachment.
---
----@param name string # The name of the attachment.
----@return Vector # The forward unit vector of the attachment in world space.
+---@param name string # The name of the attachment
+---@return Vector # The forward unit vector of the attachment in world space
function CBaseAnimating:GetAttachmentNameForward(name)
return self:GetAttachmentForward(self:ScriptLookupAttachment(name))
end
@@ -594,7 +601,7 @@ end
---
---Sets the absolute world velocity of the entity.
---
----@param velocity Vector The target velocity in units/second.
+---@param velocity Vector The target velocity in units/second
function CBaseEntity:SetAbsVelocity(velocity)
local currentVelocity = GetPhysVelocity(self)
@@ -606,8 +613,8 @@ end
---
---Tests if the OBB of this entity intersects with the OBB of another entity.
---
----@param other EntityHandle # The other entity.
----@return boolean # True if the OBB of this entity intersects with the OBB of another entity.
+---@param other EntityHandle # The other entity
+---@return boolean # `true` if the OBB of this entity intersects with the OBB of the other entity
function CBaseEntity:OBBvsOBB(other)
return OBBvsOBB(
GetEntityOBBData(self), self:GetOrigin(), self:GetAngles(),
@@ -620,8 +627,8 @@ end
---
---The AABB is defined by the entity's bounding mins/maxs and its current origin/angles.
---
----@param other EntityHandle # The other entity.
----@return boolean # True if the AABB of this entity intersects with the OBB of another entity.
+---@param other EntityHandle # The other entity
+---@return boolean # `true` if the AABB of this entity intersects with the OBB of the other entity
function CBaseEntity:AABBvsOBB(other)
local aMin, aMax = GetEntityAABB(self)
@@ -636,8 +643,8 @@ end
---
---The AABB is defined by the entity's bounding mins/maxs and its current origin/angles.
---
----@param other EntityHandle # The other entity.
----@return boolean # True if the AABB of this entity intersects with the AABB of another entity.
+---@param other EntityHandle # The other entity
+---@return boolean # `true` if the AABB of this entity intersects with the AABB of the other entity
function CBaseEntity:AABBvsAABB(other)
local aMin, aMax = GetEntityAABB(self)
local bMin, bMax = GetEntityAABB(other)
diff --git a/scripts/vscripts/alyxlib/extensions/npc.lua b/scripts/vscripts/alyxlib/extensions/npc.lua
index 42ff2b1..8e4fde1 100644
--- a/scripts/vscripts/alyxlib/extensions/npc.lua
+++ b/scripts/vscripts/alyxlib/extensions/npc.lua
@@ -5,7 +5,7 @@
Extension for NPC entities.
Some functions are specific to only one entity class such as `npc_combine_s`, check the individual function descriptions.
- If not using `vscripts/alyxlib/init.lua`, load this file at game start using the following line:
+ If not using `alyxlib/init.lua`, load this file at game start using the following line:
require "alyxlib.extensions.npc"
]]
@@ -17,7 +17,7 @@ require "alyxlib.extensions.entities"
local version = "v1.2.0"
---
----Schedule enum table for use with CAI_BaseNPC:StartSchedule().
+---Schedule enum table for use with [CAI_BaseNPC:StartSchedule](lua://CAI_BaseNPC.StartSchedule).
---
Schedule = {
---@enum ScheduleType
@@ -48,14 +48,14 @@ Schedule = {
}
---
----Create and start a new schedule for this NPC.
+---Creates and starts a new schedule for this NPC.
---
----@param state ScheduleState # The NPC state that should be set.
----@param type ScheduleType # The type of schedule to perform.
----@param interruptability ScheduleInterruptability # What should interrupt the NPC from the schedule.
----@param reacquire boolean # If the NPC should reacquire the schedule after being interrupted.
----@param goal EntityHandle|Vector # Worldspace position or entity goal (entity origin will be used).
----@return EntityHandle # The schedule entity.
+---@param state ScheduleState # The NPC state that should be set
+---@param type ScheduleType # The type of schedule to perform
+---@param interruptability ScheduleInterruptability # What should interrupt the NPC from the schedule
+---@param reacquire boolean # If the NPC should reacquire the schedule after being interrupted
+---@param goal EntityHandle|Vector # Worldspace position or entity goal (entity origin will be used)
+---@return EntityHandle # The new `aiscripted_schedule`
function CAI_BaseNPC:StartSchedule(state, type, interruptability, reacquire, goal)
local goalName
@@ -89,8 +89,8 @@ end
---
---Stops the given schedule for this NPC.
---
----@param schedule EntityHandle # The previously created schedule.
----@param dontKill? boolean # If true the schedule will not be killed at the same time.
+---@param schedule EntityHandle # The previously created schedule
+---@param dontKill? boolean # If true the schedule will not be killed at the same time
function CAI_BaseNPC:StopSchedule(schedule, dontKill)
if IsEntity(schedule, true) then
schedule:EntFire("StopSchedule")
@@ -100,30 +100,32 @@ function CAI_BaseNPC:StopSchedule(schedule, dontKill)
end
end
----Set state of the NPC.
----@param state ScheduleState
+---
+---Sets the state of the NPC by creating a new schedule.
+---
+---@param state ScheduleState # The NPC state that should be set
function CAI_BaseNPC:SetState(state)
local schedule = self:StartSchedule(state, 0, 0, false, self)
end
---
----Get if this NPC has an enemy target.
+---Gets if this NPC has an enemy target.
---
---This function only works with entities that have `enemy` or `distancetoenemy` criteria.
---
----@return boolean # True if the NPC has an enemy target.
+---@return boolean # True if the NPC has an enemy target
function CAI_BaseNPC:HasEnemyTarget()
local criteria = self:GetCriteria()
return criteria.enemy ~= nil or criteria.distancetoenemy ~= 16384
end
---
----Estimate the enemy that this NPC is fighting using its criteria values.
+---Estimates the enemy that this NPC is fighting using its criteria values
---
----This function only works with entities that have `enemy` criteria; "npc_combine_s", "npc_zombine", "npc_zombie_blind".
+---This function only works with entities that have `enemy` criteria, e.g. "npc_combine_s", "npc_zombine", "npc_zombie_blind".
---
----@param distanceTolerance? number # Discrepancy allowed when comparing distance to enemy. Default 1
----@return EntityHandle? # Estimated enemy target.
+---@param distanceTolerance? number # Discrepancy allowed when comparing distance to enemy; default is `1`
+---@return EntityHandle? # Estimated enemy target
function CAI_BaseNPC:EstimateEnemyTarget(distanceTolerance)
distanceTolerance = distanceTolerance or 1
local criteria = self:GetCriteria()
@@ -142,16 +144,21 @@ function CAI_BaseNPC:EstimateEnemyTarget(distanceTolerance)
return nil
end
+---
+---Relationship disposition types.
+---
---@alias RelationshipDisposition
---| "D_HT" # Hate
---| "D_FR" # Fear
---| "D_LI" # Like
---| "D_NU" # Neutral
----Set the relationship of this NPC with a targetname or classname.
+---
+---Sets the relationship of this NPC with another entity.
+---
---@param target string|EntityHandle # Targetname, classname or entity.
---@param disposition RelationshipDisposition # Type of relationship with `target`.
----@param priority? number # How much the Subject(s) should Like/Hate/Fear the Target(s). Higher priority = stronger feeling. Default is 0.
+---@param priority? number # How much the Subject(s) should Like/Hate/Fear the Target(s). Higher priority = stronger feeling; default is 0.
function CAI_BaseNPC:SetRelationship(target, disposition, priority)
if IsEntity(target) then
target = target--[[@as EntityHandle]]:GetName()
@@ -179,11 +186,11 @@ local livingClasses = {
}
---
----Get if this NPC is a creature, e.g. combine, headcrab, player
+---Checks if this NPC is a creature, e.g. combine, headcrab, player
---
---Will return false for all other class types, such as npc_turret and npc_manhack.
---
----@return boolean
+---@return boolean # True if this NPC is a creature
function CAI_BaseNPC:IsCreature()
return vlua.find(livingClasses, self:GetClassname()) ~= nil
end
@@ -195,9 +202,9 @@ local combineClasses = {
}
---
----Get if this NPC is a combine creature.
+---Checks if this NPC is a Combine creature.
---
----@return boolean
+---@return boolean # True if this NPC is Combine
function CAI_BaseNPC:IsCombine()
return vlua.find(combineClasses, self:GetClassname()) ~= nil
end
@@ -215,17 +222,17 @@ local xenClasses = {
}
---
----Get if this NPC is a Xen creature.
+---Checks if this NPC is a Xen creature.
---
----@return boolean
+---@return boolean # True if this NPC is Xen
function CAI_BaseNPC:IsXen()
return vlua.find(xenClasses, self:GetClassname()) ~= nil
end
---
----Get all members of the same squad as this NPC, including this NPC.
+---Gets all members of the same squad as this NPC, including this NPC.
---
----@return CAI_BaseNPC[]
+---@return CAI_BaseNPC[] # List of NPC members
function CAI_BaseNPC:GetSquadMembers()
local squad = self:GetSquad()
local members = {}
diff --git a/scripts/vscripts/alyxlib/extensions/qangle.lua b/scripts/vscripts/alyxlib/extensions/qangle.lua
index f274708..fd9e9f9 100644
--- a/scripts/vscripts/alyxlib/extensions/qangle.lua
+++ b/scripts/vscripts/alyxlib/extensions/qangle.lua
@@ -4,7 +4,7 @@
Provides QAngle class extension methods.
- If not using `vscripts/alyxlib/init.lua`, load this file at game start using the following line:
+ If not using `alyxlib/init.lua`, load this file at game start using the following line:
require "alyxlib.extensions.qangle"
]]
diff --git a/scripts/vscripts/alyxlib/extensions/string.lua b/scripts/vscripts/alyxlib/extensions/string.lua
index 296ca83..c153d4b 100644
--- a/scripts/vscripts/alyxlib/extensions/string.lua
+++ b/scripts/vscripts/alyxlib/extensions/string.lua
@@ -4,7 +4,7 @@
Provides string class extension methods.
- If not using `vscripts/alyxlib/init.lua`, load this file at game start using the following line:
+ If not using `alyxlib/init.lua`, load this file at game start using the following line:
require "alyxlib.extensions.string"
]]
@@ -18,26 +18,32 @@ local function escape(s)
return (s:gsub("([^%w])", "%%%1"))
end
+---
+---Checks if a string starts with a substring.
+---
+---@param s string # The string to check
+---@param substr string # The substring to check
+---@return boolean # `true` if the string starts with the substring, `false` otherwise
function string.startswith(s, substr)
return s:sub(1, #substr) == substr
end
---
----Gets if a string ends with a substring.
+---Checks if a string ends with a substring.
---
----@param s string
----@param substr string
----@return boolean
+---@param s string # The string to check
+---@param substr string # The substring to check
+---@return boolean # `true` if the string ends with the substring, `false` otherwise
function string.endswith(s, substr)
return substr == "" or s:sub(-#substr) == substr
end
---
----Split an input string using a raw pattern string. No changes are made to the pattern.
+---Splits a string using a raw pattern string. No changes are made to the pattern.
---
----@param s string
----@param pattern string # Split pattern.
----@return string[]
+---@param s string # String to split.
+---@param pattern string # Split pattern
+---@return string[] # Array of split strings
function string.splitraw(s, pattern)
local t = {}
for str in s:gmatch(pattern) do
@@ -47,13 +53,17 @@ function string.splitraw(s, pattern)
end
---
----Split an input string using a separator string.
+---Splits a string using a separator string.
+---
+---If `sep` is omitted, splits on whitespace (`%s`).
+---
+---Uses Lua pattern matching; escape special characters if needed.
---
---@link https://stackoverflow.com/a/7615129
---
----@param s string
----@param sep string? # String to split by. Default is whitespace.
----@return string[]
+---@param s string # String to split
+---@param sep string? # String to split by
+---@return string[] # Array of split strings
function string.split(s, sep)
if sep == nil then
sep = '%s'
@@ -65,12 +75,13 @@ end
---
---Truncates a string to a maximum length.
+---
---If the string is shorter than `len` the original string is returned.
---
----@param s string
----@param len integer # Maximum length the string can be.
----@param replacement? string # Suffix for long strings. Default is '...'
----@return string
+---@param s string # String to truncate
+---@param len integer # Maximum length the string can be
+---@param replacement? string # Suffix for long strings. Default is `...`
+---@return string # The truncated string
function string.truncate(s, len, replacement)
replacement = replacement or "..."
if #s > len then
@@ -149,9 +160,9 @@ end
---
---If `onlyFirstLetter` is false or not provided, it capitalizes all letters.
---
----@param s string # The input string to be capitalized.
----@param onlyFirstLetter boolean # (optional) If true, only the first letter is capitalized. Default is false.
----@return string # The capitalized string.
+---@param s string # The string to be capitalized
+---@param onlyFirstLetter? boolean # If true, only the first letter is capitalized
+---@return string # The capitalized string
function string.capitalize(s, onlyFirstLetter)
if onlyFirstLetter then
local upper = s:gsub("^%l", string.upper)
diff --git a/scripts/vscripts/alyxlib/extensions/template.lua b/scripts/vscripts/alyxlib/extensions/template.lua
index 22ab7c7..dbb0c3e 100644
--- a/scripts/vscripts/alyxlib/extensions/template.lua
+++ b/scripts/vscripts/alyxlib/extensions/template.lua
@@ -4,7 +4,7 @@
Provides point_template extension methods.
- If not using `vscripts/alyxlib/init.lua`, load this file at game start using the following line:
+ If not using `alyxlib/init.lua`, load this file at game start using the following line:
require "alyxlib.extensions.template"
]]
diff --git a/scripts/vscripts/alyxlib/extensions/vector.lua b/scripts/vscripts/alyxlib/extensions/vector.lua
index ec65aac..9d31290 100644
--- a/scripts/vscripts/alyxlib/extensions/vector.lua
+++ b/scripts/vscripts/alyxlib/extensions/vector.lua
@@ -4,7 +4,7 @@
Provides Vector class extension methods.
- If not using `vscripts/alyxlib/init.lua`, load this file at game start using the following line:
+ If not using `alyxlib/init.lua`, load this file at game start using the following line:
require "alyxlib.extensions.vector"
]]
@@ -22,7 +22,7 @@ local zero = Vector(0, 0, 0)
---
---Calculates the perpendicular vector to the current vector.
---
----@return Vector # The perpendicular vector.
+---@return Vector # The perpendicular vector
function meta:Perpendicular()
if self.y == 0 and self.z == 0 then
if self.x == 0 then
@@ -37,11 +37,11 @@ function meta:Perpendicular()
end
---
----Check if the current vector is perpendicular to another vector.
+---Checks if the current vector is perpendicular to another vector.
---
----@param vector Vector # The other vector to check perpendicularity against.
----@param tolerance? number # (optional) The tolerance value for the dot product comparison. Default is 1e-8.
----@return boolean # True if the vectors are perpendicular, false otherwise.
+---@param vector Vector # The other vector to check perpendicularity against
+---@param tolerance? number # (optional) The tolerance value for the dot product comparison. Default is `1e-8`
+---@return boolean # True if the vectors are perpendicular, false otherwise
function meta:IsPerpendicularTo(vector, tolerance)
tolerance = tolerance or 1e-8
return math.abs(self:Dot(vector)) <= tolerance
@@ -50,8 +50,8 @@ end
---
---Checks if the current vector is parallel to the given vector.
---
----@param vector Vector # The vector to compare with.
----@return boolean # True if the vectors are parallel, false otherwise.
+---@param vector Vector # The vector to compare with
+---@return boolean # True if the vectors are parallel, false otherwise
function meta:IsParallelTo(vector)
-- Treat zero vectors as parallel to any vector
if self == zero or vector == zero then
@@ -62,11 +62,11 @@ function meta:IsParallelTo(vector)
end
---
----Spherical linear interpolation between the calling vector and the target vector over t = [0, 1].
+---Spherical linear interpolation between the calling vector and the target vector over `t = [0, 1]`.
---
----@param target Vector # The target vector to interpolate towards.
----@param t number # The interpolation factor, ranging from 0 to 1.
----@return Vector # The resulting vector after spherical linear interpolation.
+---@param target Vector # The target vector to interpolate towards
+---@param t number # The interpolation factor, ranging from 0 to 1
+---@return Vector # The resulting vector after spherical linear interpolation
function meta:Slerp(target, t)
local dot = self:Dot(target)
dot = math.max(-1, math.min(1, dot))
@@ -83,19 +83,18 @@ end
---
---Translates a vector within a local coordinate system.
----This function computes a new vector by applying an offset relative to the local axes defined by the forward, right, and up direction vectors.
----
----@param offset Vector # The translation offset vector. This defines how much to move along the forward, right, and up directions.
---- - `offset.x`: Translation along the forward vector.
---- - `offset.y`: Translation along the right vector.
---- - `offset.z`: Translation along the up vector.
---
----@param forward Vector # The forward direction of the local coordinate system.
----@param right Vector # The right direction of the local coordinate system.
----@param up Vector # The up direction of the local coordinate system.
+---This function computes a new vector by applying an offset relative to the local axes defined by the forward, right, and up direction vectors.
---
----@return Vector # A new vector representing the translated position.
+---@param offset Vector # The translation offset vector. This defines how much to move along the forward, right, and up directions
+--- - `offset.x`: Translation along the forward vector
+--- - `offset.y`: Translation along the right vector
+--- - `offset.z`: Translation along the up vector
---
+---@param forward Vector # The forward direction of the local coordinate system
+---@param right Vector # The right direction of the local coordinate system
+---@param up Vector # The up direction of the local coordinate system
+---@return Vector # A new vector representing the translated position
function meta:LocalTranslate(offset, forward, right, up)
local x = self.x + offset.x * forward.x + offset.y * right.x + offset.z * up.x
local y = self.y + offset.x * forward.y + offset.y * right.y + offset.z * up.y
@@ -106,8 +105,8 @@ end
---
---Calculates the angle difference in degrees between the calling vector and the given vector. This is always the smallest angle.
---
----@param vector Vector # The vector to calculate the angle difference with.
----@return number # Angle difference in degrees.
+---@param vector Vector # The vector to calculate the angle difference with
+---@return number # Angle difference in degrees
function meta:AngleDiff(vector)
local denominator = math.sqrt(self:Length() * vector:Length())
if denominator < 1e-15 then
@@ -120,9 +119,9 @@ end
---
---Calculates the signed angle difference between the calling vector and the given vector around the specified axis.
---
----@param vector Vector # The vector to calculate the angle difference with.
----@param axis? Vector # The axis of rotation around which the angle difference is calculated.
----@return number # The signed angle difference in degrees.
+---@param vector Vector # The vector to calculate the angle difference with
+---@param axis? Vector # The axis of rotation around which the angle difference is calculated
+---@return number # The signed angle difference in degrees
function meta:SignedAngleDiff(vector, axis)
axis = axis or Vector(0, 0, 1)
local unsignedAngle = self:AngleDiff(vector)
@@ -145,6 +144,7 @@ end
---
---Returns the squared length (magnitude) of the vector.
+---
---More efficient than calculating the actual length as it avoids using `sqrt()`.
---
---@return number
@@ -155,9 +155,10 @@ end
---
---Checks if this vector is similar to another vector within a given tolerance.
---
----@param vector Vector # The vector to compare against.
----@param tolerance? number # The tolerance within which the vectors are considered similar. Default is 1e-5. See [math.isclose](lua://math.isclose)
----@return boolean # Returns `true` if the vectors are similar within the tolerance, otherwise `false`.
+---@see math.isclose
+---@param vector Vector # The vector to compare against
+---@param tolerance? number # The tolerance within which the vectors are considered similar. Default is `1e-5`
+---@return boolean # Returns `true` if the vectors are similar within the tolerance, otherwise `false`
function meta:IsSimilarTo(vector, tolerance)
tolerance = tolerance or 1e-5
return IsVector(vector)
@@ -169,7 +170,7 @@ end
---
---Creates a copy of the vector.
---
----@return Vector # A new vector with the same components as the original.
+---@return Vector # A new vector with the same components as the original
function meta:Clone()
return Vector(self.x, self.y, self.y)
end
diff --git a/scripts/vscripts/alyxlib/globals.lua b/scripts/vscripts/alyxlib/globals.lua
index 9195050..8bbbaf5 100644
--- a/scripts/vscripts/alyxlib/globals.lua
+++ b/scripts/vscripts/alyxlib/globals.lua
@@ -4,11 +4,9 @@
Provides common global functions used throughout extravaganza libraries.
- If not using `vscripts/alyxlib/init.lua`, load this file at game start using the following line:
+ If not using `alyxlib/init.lua`, load this file at game start using the following line:
- ```lua
require "alyxlib.globals"
- ```
]]
-- These are expected by globals
@@ -31,7 +29,7 @@ local _version = "v2.7.0"
---
---List of registered addons using AlyxLib.
---
----Requires the developer to use the `RegisterAlyxLibAddon` function in their addon.
+---Requires the developer to use the [RegisterAlyxLibAddon](lua://RegisterAlyxLibAddon) function in their addon.
---
---@type AlyxLibAddon[]
AlyxLibAddons = {}
@@ -42,7 +40,7 @@ AlyxLibAddons = {}
---@param name string # Full display name of the addon, e.g. "My New Addon"
---@param version string # SemVer version string of the addon, e.g. "v1.2.3"
---@param workshopID? string # The ID of the addon on the Steam workshop
----@param shortName? string # Short unique name of the addon without spaces, e.g. "myaddon". Defaults to `name` without spaces and converted to lowercase\
+---@param shortName? string # Short unique name of the addon without spaces, e.g. "myaddon". Defaults to `name` without spaces and converted to lowercase
---@param minAlyxLibVersion? string # Minimum AlyxLib version that this addon works with, defaults to "v1.0.0"
---@param maxAlyxLibVersion? string # Maximum AlyxLib version that this addon works with, defaults to `ALYXLIB_VERSION`
---@return integer # The index of the addon for use in other AlyxLib functions
@@ -88,8 +86,8 @@ end
---It compares the versions based on their `major`, `minor`, and `patch` components.
---If a version is incomplete, the missing components are assumed to be 0.
---
----@param v1 string # The first version string to compare. May include leading "v" and whitespace, and may have missing `minor` or `patch` components.
----@param v2 string # The second version string to compare. Similar format and rules to `v1`.
+---@param v1 string # The first version string to compare. May include leading "v" and whitespace, and may have missing `minor` or `patch` components
+---@param v2 string # The second version string to compare. Similar format and rules to `v1`
---
---@return -1|0|1 #
--- - `-1` if `v1` is older than `v2`.
@@ -116,11 +114,11 @@ end
---
----Get the file name of the current script without folders or extension. E.g. `util.util`
+---Gets the file name of the current script without folders or extension. E.g. `util.util`
---
----@param sep? string # Separator character, default is '.'
----@param level? (integer|function)? # Function level, [View documents](command:extension.lua.doc?["en-us/51/manual.html/pdf-debug.getinfo"])
----@return string
+---@param sep? string # Separator character (default: `.`)
+---@param level? integer|function # Function level, [View documents](command:extension.lua.doc?["en-us/51/manual.html/pdf-debug.getinfo"])
+---@return string # File name
function GetScriptFile(sep, level)
sep = sep or "."
local sys_sep = package.config:sub(1,1)
@@ -137,9 +135,9 @@ function GetScriptFile(sep, level)
end
---
----Get the list of enabled addons from the `default_enabled_addons_list` Convar.
+---Gets the list of enabled addons from the `default_enabled_addons_list` convar.
---
----@return string[]
+---@return string[] # List of enabled addons
function GetEnabledAddons()
local addons = {}
---@type string
@@ -160,7 +158,7 @@ function IsAddonEnabled(workshopID)
end
---
----Get if the given `handle` value is an entity, regardless of if it's still alive.
+---Checks if the given `handle` value is an entity, regardless of if it's still alive.
---
---A common usage is replacing the often used entity check:
---
@@ -170,34 +168,34 @@ end
---
--- if IsEntity(entity, true) then
---
----@param handle EntityHandle|any
----@param checkValidity? boolean # Optionally check validity with IsValidEntity.
+---@param value EntityHandle|any # The value to check
+---@param checkValidity? boolean # Optionally check validity with [IsValidEntity](lua://IsValidEntity)
---@return boolean
-function IsEntity(handle, checkValidity)
- return (type(handle) == "table" and handle.__self and type(handle.__self) == "userdata") and (not checkValidity or IsValidEntity(handle))
+function IsEntity(value, checkValidity)
+ return (type(value) == "table" and value.__self and type(value.__self) == "userdata") and (not checkValidity or IsValidEntity(value))
end
---
----Add an output to a given entity `handle`.
+---Adds an output to a given `entity`.
---
----@param handle EntityHandle|string # The entity to add the `output` to.
----@param output string # The output name to add.
----@param target EntityHandle|string # The entity the output should target, either handle or targetname.
----@param input string # The input name on `target`.
----@param parameter? string # The parameter override for `input`.
----@param delay? number # Delay for the output in seconds.
----@param activator? EntityHandle # Activator for the output.
----@param caller? EntityHandle # Caller for the output.
----@param fireOnce? boolean # If the output should only fire once.
-function AddOutput(handle, output, target, input, parameter, delay, activator, caller, fireOnce)
+---@param entity EntityHandle|string # The entity to add the `output` to
+---@param output string # The output name to add
+---@param target EntityHandle|string # The entity the output should target, either handle or targetname
+---@param input string # The input name on `target`
+---@param parameter? string # The parameter override for `input` (default: `""`)
+---@param delay? number # Delay for the output in seconds (default: `0`)
+---@param activator? EntityHandle # Activator for the output (default: `nil`)
+---@param caller? EntityHandle # Caller for the output (default: `nil`)
+---@param fireOnce? boolean # If the output should only fire once (default: `false`)
+function AddOutput(entity, output, target, input, parameter, delay, activator, caller, fireOnce)
if IsEntity(target) then target = target:GetName() end
parameter = parameter or ""
delay = delay or 0
local output_str = output..">"..target..">"..input..">"..parameter..">"..delay..">"..(fireOnce and 1 or -1)
- if type(handle) == "string" then
- DoEntFire(handle, "AddOutput", output_str, 0, activator or nil, caller or nil)
+ if type(entity) == "string" then
+ DoEntFire(entity, "AddOutput", output_str, 0, activator or nil, caller or nil)
else
- DoEntFireByInstanceHandle(handle, "AddOutput", output_str, 0, activator or nil, caller or nil)
+ DoEntFireByInstanceHandle(entity, "AddOutput", output_str, 0, activator or nil, caller or nil)
end
end
CBaseEntity.AddOutput = AddOutput
@@ -205,8 +203,8 @@ CBaseEntity.AddOutput = AddOutput
---
---Checks if the module/script exists.
---
----@param name? string
----@return boolean
+---@param name? string # Name of the module
+---@return boolean # `true` if the module exists, `false` otherwise
---@diagnostic disable-next-line: lowercase-global
function module_exists(name)
if name == nil then return false end
@@ -230,9 +228,9 @@ end
---
---If the module fails to load then the callback is not executed and no error is thrown, but a warning is displayed in the console.
---
----@param modname string
----@param callback fun(mod_result: unknown)?
----@return unknown
+---@param modname string # Name of the module/script
+---@param callback fun(mod_result: unknown)? # Callback function
+---@return unknown|nil # The value returned by the module, or `nil` if the module fails to load
---@diagnostic disable-next-line: lowercase-global
function ifrequire(modname, callback)
---@TODO: Consider using module_exists
@@ -253,27 +251,27 @@ function ifrequire(modname, callback)
end
---
----Execute a script file. Included in the current scope by default.
+---Executes a script file. Included in the current scope by default.
---
----@param scriptFileName string
----@param scope? ScriptScope
----@return boolean
+---@param scriptFileName string # Name of the script
+---@param scope? ScriptScope # Scope to include the script in
+---@return boolean # `true` if the script was successfully included, `false` otherwise
function IncludeScript(scriptFileName, scope)
return DoIncludeScript(scriptFileName, scope or getfenv(2))
end
---
----Gets if the game was started in VR mode.
+---Checks if the game was started in VR mode.
---
----@return boolean
+---@return boolean # `true` if the game was started in VR mode, `false` otherwise
function IsVREnabled()
return GlobalSys:CommandLineCheck('-vr')
end
---
----Gets if the game was started with `+vr_enable_fake_vr 1`.
+---Checks if the game was started with `+vr_enable_fake_vr 1`.
---
----@return boolean
+---@return boolean # `true` if the game was started in fake VR mode, `false` otherwise
function IsFakeVREnabled()
return GlobalSys:CommandLineInt("+vr_enable_fake_vr", 0) == 1
end
@@ -281,7 +279,7 @@ end
---
---Prints all arguments with spaces between instead of tabs.
---
----@param ... any
+---@param ... any # Any number of arguments
---@diagnostic disable-next-line: lowercase-global
function prints(...)
local args = {...}
@@ -301,7 +299,7 @@ end
---
---Prints all arguments on a new line instead of tabs.
---
----@param ... any
+---@param ... any # Any number of arguments
---@diagnostic disable-next-line: lowercase-global
function printn(...)
local args = {...}
@@ -313,7 +311,7 @@ end
---
---Prints all arguments if convar "developer" is greater than 0.
---
----@param ... any
+---@param ... any # Any number of arguments
---@diagnostic disable-next-line: lowercase-global
function devprint(...)
if Convars:GetInt("developer") > 0 then
@@ -324,7 +322,7 @@ end
---
---Prints all arguments on a new line instead of tabs if convar "developer" is greater than 0.
---
----@param ... any
+---@param ... any # Any number of arguments
---@diagnostic disable-next-line: lowercase-global
function devprints(...)
if Convars:GetInt("developer") > 0 then
@@ -335,7 +333,7 @@ end
---
---Prints all arguments with spaces between instead of tabs if convar "developer" is greater than 0.
---
----@param ... any
+---@param ... any # Any number of arguments
---@diagnostic disable-next-line: lowercase-global
function devprintn(...)
if Convars:GetInt("developer") > 0 then
@@ -346,7 +344,7 @@ end
---
---Prints all arguments if convar "developer" is greater than 1.
---
----@param ... any
+---@param ... any # Any number of arguments
---@diagnostic disable-next-line: lowercase-global
function devprint2(...)
if Convars:GetInt("developer") > 1 then
@@ -357,7 +355,7 @@ end
---
---Prints all arguments on a new line instead of tabs if convar "developer" is greater than 1.
---
----@param ... any
+---@param ... any # Any number of arguments
---@diagnostic disable-next-line: lowercase-global
function devprints2(...)
if Convars:GetInt("developer") > 1 then
@@ -368,7 +366,7 @@ end
---
---Prints all arguments with spaces between instead of tabs if convar "developer" is greater than 1.
---
----@param ... any
+---@param ... any # Any number of arguments
---@diagnostic disable-next-line: lowercase-global
function devprintn2(...)
if Convars:GetInt("developer") > 1 then
@@ -379,7 +377,7 @@ end
---
---Prints a warning in the console, along with a vscript print if inside tools mode.
---
----@param ... any
+---@param ... any # Any number of arguments
function warn(...)
local str = table.concat({...}, " ")
Warning(str .. "\n")
@@ -392,7 +390,7 @@ end
---Prints a warning in the console, along with a vscript print if inside tools mode.
---But only if convar "developer" is greater than 0.
---
----@param ... any
+---@param ... any # Any number of arguments
---@diagnostic disable-next-line: lowercase-global
function devwarn(...)
if Convars:GetInt("developer") > 0 then
@@ -418,9 +416,9 @@ end
--- -- Or with alternate name
--- Expose(TriggerRelay, "RelayInput")
---
----@param func function # The function to expose.
----@param name? string # Optionally the name of the function for faster processing.
----@param scope? table # Optionally the explicit scope to put the exposed function in.
+---@param func function # The function to expose
+---@param name? string # Optionally the name of the function for faster processing
+---@param scope? table # Optionally the explicit scope to put the exposed function in
function Expose(func, name, scope)
local fenv = getfenv(func)
-- if name is empty then find the name
@@ -456,10 +454,10 @@ local base_vector = Vector()
local vector_meta = getmetatable(base_vector)
---
----Get if a value is a `Vector`
+---Checks if a value is a [Vector](lua://Vector).
---
----@param value any
----@return boolean
+---@param value any # Value to check
+---@return boolean # `true` if the value is a [Vector](lua://Vector), `false` otherwise
function IsVector(value)
return getmetatable(value) == vector_meta
end
@@ -468,10 +466,10 @@ local base_qangle = QAngle()
local qangle_meta = getmetatable(base_qangle)
---
----Get if a value is a `QAngle`
+---Checks if a value is a [QAngle](lua://QAngle).
---
----@param value any
----@return boolean
+---@param value any # Value to check
+---@return boolean # `true` if the value is a [QAngle](lua://QAngle), `false` otherwise
function IsQAngle(value)
return getmetatable(value) == qangle_meta
end
@@ -484,8 +482,8 @@ end
---`Vector`,
---`QAngle`
---
----@param tbl table
----@return table
+---@param tbl table # Table to copy
+---@return table # A copy of `tbl`
function DeepCopyTable(tbl)
-- print("Deep copy inside", tbl)
local t = {}
@@ -511,11 +509,12 @@ end
---
---Searches for `value` in `tbl` and sets the associated key to `nil`, returning the key if found.
---
----If your table is an array you should use `ArrayRemove` instead.
+---If your table is an array you should use [ArrayRemove](lua://ArrayRemove) instead.
---
----@param tbl table
----@param value any
----@return any
+---@see ArrayRemove
+---@param tbl table # Table to search
+---@param value any # Value to search for
+---@return any # The key of `value` if found, `nil` otherwise
function TableRemove(tbl, value)
local k = vlua.find(tbl, value)
if k then
@@ -528,9 +527,9 @@ end
---
---Returns a random key/value pair from a unordered table.
---
----@param tbl table # Table to get a random pair from.
----@return any key # Random key selected.
----@return any value # Value linked to the random key.
+---@param tbl table # Table to get a random pair from
+---@return any key # Random key selected
+---@return any value # Value linked to the random key
function TableRandom(tbl)
local count = 0
local selectedKey
@@ -548,11 +547,11 @@ function TableRandom(tbl)
end
---
----Returns all the keys of a table as a new ordered array.
+---Returns all keys of a table as a new ordered array.
---
---@generic K
----@param tbl table
----@return K[]
+---@param tbl table # Table to get keys from
+---@return K[] # List of keys
function TableKeys(tbl)
local array = {}
for key, _ in pairs(tbl) do
@@ -562,11 +561,11 @@ function TableKeys(tbl)
end
---
----Returns all the value of a table as a new ordered array.
+---Returns all values in a table as a new ordered array.
---
---@generic V
----@param tbl table
----@return V[]
+---@param tbl table # Table to get values from
+---@return V[] # List of values
function TableValues(tbl)
local array = {}
for _, value in pairs(tbl) do
@@ -578,12 +577,12 @@ end
---
---Returns the size of a table by counting all keys.
---
----@param tbl table # The table to count.
----@return number # The size of the table.
+---@param tbl table # The table to count
+---@return number # The size of the table
function TableSize(tbl)
if tbl == nil then return 0 end
local size = 0
- for _, l in pairs(tbl) do
+ for _, _ in pairs(tbl) do
size = size + 1
end
return size
@@ -592,9 +591,9 @@ end
---
---Collects all values for a specific key from a list of tables.
---
----@param tbl table[] # List of tables.
----@param key any # Key to get values from.
----@return any[] # List of values found for the key.
+---@param tbl table[] # List of tables
+---@param key any # Key to get values from
+---@return any[] # List of values found for `key`
function TablePluck(tbl, key)
local out = {}
for _, v in ipairs(tbl) do
@@ -608,9 +607,9 @@ end
---
---Returns the index of the first value that matches the predicate.
---
----@param list table
----@param predicate fun(value:any):boolean
----@return any
+---@param list table # List to search
+---@param predicate fun(value:any):boolean # Predicate function
+---@return any # Index of the first value that matches the predicate
function TableFindKey(list, predicate)
for i, v in pairs(list) do
if predicate(v) then
@@ -624,11 +623,11 @@ end
---Returns a random value from an array.
---
---@generic T
----@param array T[] # Array to get a value from.
----@param min? integer # Optional minimum bound.
----@param max? integer # Optional maximum bound.
----@return T one # The random value.
----@return integer two # The random index.
+---@param array T[] # Array to get a value from
+---@param min? integer # Optional minimum bound
+---@param max? integer # Optional maximum bound
+---@return T one # The random value
+---@return integer two # The random index
function ArrayRandom(array, min, max)
local i = RandomInt(min or 1, max or #array)
return array[i], i
@@ -639,7 +638,7 @@ end
---
---@source https://stackoverflow.com/a/68486276
---
----@param array any[]
+---@param array any[] # Array to shuffle
function ArrayShuffle(array)
for i = #array, 2, -1 do
local j = RandomInt(1, i)
@@ -653,9 +652,9 @@ end
---This is exponentially faster than `table.remove` for large arrays.
---
---@generic T
----@param array T # The array to remove from.
----@param pos integer # Position to remove at.
----@return T # The same array passed in.
+---@param array T # The array to remove from
+---@param pos integer # Position to remove at
+---@return T # The same array passed in
function ArrayRemove(array, pos)
local j, n = 1, #array
@@ -711,8 +710,8 @@ end
---@generic T1
---@generic T2
---@param array1 T1[] # Base array
----@param array2 T2[] # Array which will be appended onto the base array.
----@return T1[]|T2[] # The new appended array.
+---@param array2 T2[] # Array which will be appended onto the base array
+---@return T1[]|T2[] # The new appended array
function ArrayAppend(array1, array2)
array1 = vlua.clone(array1)
for _, v in ipairs(array2) do
@@ -728,8 +727,8 @@ end
---
---@generic T
---@param array T[] # Base array
----@param ... T[] # Any arrays to add.
----@return T[] # The new appended array.
+---@param ... T[] # Any arrays to add
+---@return T[] # The new appended array
function ArrayAppends(array, ...)
array = vlua.clone(array)
for _, tbl in ipairs({...}) do
@@ -741,20 +740,20 @@ function ArrayAppends(array, ...)
end
---@class TraceTableLineExt : TraceTableLine
----@field ignore (EntityHandle|EntityHandle[])? # Entity or array of entities to ignore.
----@field ignoreclass (string|string[])? # Class or array of classes to ignore.
----@field ignorename (string|string[])? # Name or array of names to ignore.
----@field timeout integer? # Maxmimum number of traces before returning regardless of parameters.
----@field traces integer # Number of traces done.
----@field dontignore EntityHandle # A single entity to always hit, ignoring if it exists in `ignore`.
+---@field ignore (EntityHandle|EntityHandle[])? # Entity or array of entities to ignore
+---@field ignoreclass (string|string[])? # Class or array of classes to ignore
+---@field ignorename (string|string[])? # Name or array of names to ignore
+---@field timeout integer? # Maxmimum number of traces before returning regardless of parameters
+---@field traces integer # Number of traces done
+---@field dontignore EntityHandle # A single entity to always hit, ignoring if it exists in `ignore`
---
---Does a raytrace along a line with extended parameters.
---You ignore multiple entities as well as classes and names.
---Because the trace has to be redone multiple times, a `timeout` parameter can be defined to cap the number of traces.
---
----@param parameters TraceTableLineExt
----@return boolean
+---@param parameters TraceTableLineExt # Trace parameters
+---@return boolean # `true` if the trace was successful
function TraceLineExt(parameters)
if IsEntity(parameters.ignore) then
parameters.ignoreent = {parameters.ignore}
@@ -803,8 +802,8 @@ end
---
---Does a raytrace along a line until it hits the world or reaches the end of the line.
---
----@param parameters TraceTableLine
----@return TraceTableLine
+---@param parameters TraceTableLine # Trace parameters
+---@return TraceTableLine # Trace parameters with results
function TraceLineWorld(parameters)
local result = TraceLine(parameters)
while parameters.hit and parameters.enthit:GetClassname() ~= "worldent" do
@@ -819,8 +818,8 @@ end
---
---Does a raytrace along a line until it hits the specified entity or reaches the end of the line.
---
----@param parameters TraceTableLine
----@return TraceTableLine
+---@param parameters TraceTableLine # Trace parameters
+---@return TraceTableLine # Trace parameters with results
function TraceLineEntity(ent, parameters)
local result = TraceLine(parameters)
while parameters.hit and parameters.enthit ~= ent do
@@ -832,12 +831,14 @@ function TraceLineEntity(ent, parameters)
return parameters
end
+---
---Performs a simple line trace and returns the trace table.
----@param startpos Vector
----@param endpos Vector
----@param ignore? EntityHandle
----@param mask? integer
----@return TraceTableLine
+---
+---@param startpos Vector # Start position
+---@param endpos Vector # End position
+---@param ignore? EntityHandle # Entity to ignore
+---@param mask? integer # Trace mask
+---@return TraceTableLine # Trace table with results
function TraceLineSimple(startpos, endpos, ignore, mask)
---@type TraceTableLine
local traceTable = {
@@ -851,18 +852,18 @@ function TraceLineSimple(startpos, endpos, ignore, mask)
end
---
----Get if an entity is the world entity.
+---Checks if an entity is the world entity.
---
----@param entity EntityHandle
----@return boolean
+---@param entity EntityHandle # Entity to check
+---@return boolean # `true` if the entity is the world entity, `false` otherwise
function IsWorld(entity)
return IsEntity(entity) and entity:GetClassname() == "worldent"
end
---
----Get the world entity.
+---Gets the world entity.
---
----@return EntityHandle
+---@return EntityHandle # World entity
function GetWorld()
return Entities:FindByClassname(nil, "worldent")
end
@@ -899,9 +900,11 @@ local physicsClasses = {
"item_hlvr_weapon_generic_pistol",
}
----Get if an entity is a physical entity.
----@param entity EntityHandle
----@return boolean
+---
+---Checks if an entity is a physical entity.
+---
+---@param entity EntityHandle # Entity to check
+---@return boolean # `true` if the entity is a physical entity, `false` otherwise
function IsPhysicsObject(entity)
if IsEntity(entity) then
if entity:GetClassname() == "prop_animinteractable" then
@@ -917,11 +920,11 @@ function IsPhysicsObject(entity)
end
---
----Get if a table has a key (this essentially the same as tbl[key] ~= nil).
+---Checks if a table has a key (this essentially the same as tbl[key] ~= nil).
---
----@param tbl table
----@param key any
----@return boolean
+---@param tbl table # Table to check
+---@param key any # Key to look for
+---@return boolean # `true` if the table has the key, `false` otherwise
---@luadoc-ignore
---@diagnostic disable-next-line:lowercase-global
function haskey(tbl, key)
@@ -932,26 +935,26 @@ function haskey(tbl, key)
end
---
----Check if a value is truthy or falsy.
+---Checks if a value is truthy or falsy.
---
--- **falsy == `nil`|`false`|`0`|`""`|`{}`**
---
----@param value any # The value to be checked.
----@return boolean # Returns true if the value is truthy, false otherwise.
+---@param value any # The value to be checked
+---@return boolean # `true` if the value is truthy, `false` otherwise
---@diagnostic disable-next-line:lowercase-global
function truthy(value)
return not (value == nil or value == false or value == 0 or value == "" or value == "0" or value == "false" or (type(value) == "table" and next(value) == nil))
end
---
----Search an entity for a key using a search pattern. E.g. "getclass" will find "GetClassname"
+---Searches an entity for a key using a search pattern. E.g. "getclass" will find "GetClassname"
---
---Works with `class.lua` EntityClass entities.
---
----@param entity EntityHandle|EntityClass
----@param searchPattern string
----@return string? key # The full name of the first key matching `searchPattern`.
----@return any? value # The value of the key found.
+---@param entity EntityHandle|EntityClass # The entity to search
+---@param searchPattern string # The name pattern to search for
+---@return string? key # The full name of the first key matching `searchPattern`
+---@return any? value # The value of the key found
function SearchEntity(entity, searchPattern)
searchPattern = searchPattern:lower()
@@ -993,10 +996,10 @@ end
---
---Linearly interpolates between two angles.
---
----@param t number # The interpolation parameter, where 0 returns angle_start and 1 returns angle_end.
----@param angle_start number # The starting angle in degrees.
----@param angle_end number # The ending angle in degrees.
----@return number # The interpolated angle.
+---@param t number # The interpolation parameter, where `0` returns `angle_start` and `1` returns `angle_end`
+---@param angle_start number # The starting angle in degrees
+---@param angle_end number # The ending angle in degrees
+---@return number # The interpolated angle
function LerpAngle(t, angle_start, angle_end)
angle_start = angle_start % 360
angle_end = angle_end % 360
@@ -1010,22 +1013,30 @@ function LerpAngle(t, angle_start, angle_end)
return interpolated_angle
end
+---
+---Calculates the closest point on an entity's OBB to a position.
+---
+---This is a modified version of the original function that fixes
+---off-center models not calculating correctly.
+---
+---@param entity EntityHandle # The entity to calculate the closest point on
+---@param position Vector # The position to calculate the closest point to
+---@return Vector # The closest point on the entity's OBB to the position
function CalcClosestPointOnEntityOBBAdjusted(entity, position)
local org = entity:GetOrigin()
entity:SetOrigin(org + (entity:GetCenter() - entity:GetOrigin()))
local calcpos = CalcClosestPointOnEntityOBB(entity, position)
entity:SetOrigin(org)
return calcpos
- -- return calcpos + (entity:GetCenter() - entity:GetOrigin())
end
---
---Assigns a default value to a table which will be returned if an invalid key is accessed.
---
---@generic T
----@param tbl T # The table to which the default value will be assigned.
----@param default any # The default value to be returned for invalid keys.
----@return T # The table with the default value assigned.
+---@param tbl T # The table to which the default value will be assigned
+---@param default any # The default value to be returned for invalid keys
+---@return T # The table with the default value assigned
function DefaultTable(tbl, default)
return setmetatable(tbl, {
__index = function ()
@@ -1037,10 +1048,10 @@ end
---
---Wraps a value within a specified range.
---
----@param value number The value to be wrapped.
----@param min number # The minimum value of the range.
----@param max number # The maximum value of the range.
----@return number # The wrapped value within the specified range.
+---@param value number The value to be wrapped
+---@param min number # The minimum value of the range
+---@param max number # The maximum value of the range
+---@return number # The wrapped value within the specified range
function Wrap(value, min, max)
local range = max - min + 1
return ((value - min) % range) + min
@@ -1065,9 +1076,9 @@ end
--- return 0
--- end, 0)
---
----@param on? fun(...): any # Function called when the condition is true.
----@param off? fun(...): any # Function called when the condition is false.
----@return fun(condition: boolean, ...): any # The created toggle function.
+---@param on? fun(...): any # Function called when the condition is true
+---@param off? fun(...): any # Function called when the condition is false
+---@return fun(condition: boolean, ...): any # The created toggle function
function CreateToggleBehavior(on, off)
local flag = false
@@ -1091,10 +1102,10 @@ function CreateToggleBehavior(on, off)
end
---
----Compute the closest corner relative to a vector on the AABB of an entity.
+---Computes the closest corner relative to a vector on the AABB of an entity.
---
----@param entity EntityHandle
----@param position Vector
+---@param entity EntityHandle # The entity to calculate the closest corner on
+---@param position Vector # The vector to calculate the closest corner to
function CalcClosestCornerOnEntityAABB(entity, position)
local corners = entity:GetBoundingCorners(true)
local closestCorner = corners[1]
@@ -1123,9 +1134,9 @@ end
---If the random roll succeeds, returns `onTrue` (default: true).
---If it fails, returns `onFalse` (default: false).
---
----@param chance number # The percentage chance of success (0-100).
----@param onTrue? any # Value to return if the chance succeeds (default: true).
----@param onFalse? any # Value to return if the chance fails (default: false).
+---@param chance number # The percentage chance of success (0-100)
+---@param onTrue? any # Value to return if the chance succeeds (default: `true`)
+---@param onFalse? any # Value to return if the chance fails (default: `false`)
---@return boolean|any
function RandomChance(chance, onTrue, onFalse)
if onTrue == nil then onTrue = true end
diff --git a/scripts/vscripts/alyxlib/helpers/animation.lua b/scripts/vscripts/alyxlib/helpers/animation.lua
index 7795e75..894d54b 100644
--- a/scripts/vscripts/alyxlib/helpers/animation.lua
+++ b/scripts/vscripts/alyxlib/helpers/animation.lua
@@ -4,7 +4,7 @@
Provides functionality for animating getter/setter entity methods using curves.
- If not using `vscripts/alyxlib/init.lua`, load this file at game start using the following line:
+ If not using `alyxlib/init.lua`, load this file at game start using the following line:
require "alyxlib.helpers.animation"
]]
@@ -37,6 +37,9 @@ local localBounceIn = function(a, b, t)
return a + (b - a) * (1 - localBounceOut(0, 1, 1 - t))
end
+---
+---Curves for animating entity properties and values.
+---
---@enum Animation.Curves
Animation.Curves = {
linear = function(a, b, t)
diff --git a/scripts/vscripts/alyxlib/helpers/easyconvars.lua b/scripts/vscripts/alyxlib/helpers/easyconvars.lua
index dbfbbe4..6db727e 100644
--- a/scripts/vscripts/alyxlib/helpers/easyconvars.lua
+++ b/scripts/vscripts/alyxlib/helpers/easyconvars.lua
@@ -4,7 +4,7 @@
Allows for quick creation of convars which support persistence saving, checking GlobalSys for default values, and callbacks on value change.
- If not using `vscripts/alyxlib/init.lua`, load this file at game start using the following line:
+ If not using `alyxlib/init.lua`, load this file at game start using the following line:
require "alyxlib.helpers.easyconvars"
]]
@@ -18,21 +18,25 @@ require "alyxlib.globals"
EasyConvars = {}
EasyConvars.version = "v2.0.0"
+---
---Data of a registered convar.
+---
---@class EasyConvarsRegisteredData
----@field type EasyConvarsType # Type of the convar.
+---@field type EasyConvarsType # Type of the convar
---@field name string # Name of the convar
----@field desc? string # Description of the convar/command. Is displayed below the current value when called without a parameter.
----@field value string # Raw value of the convar.
----@field prevValue string # Previous raw value of the convar.
----@field callback? fun(val:string, ...):any? # Optional callback function whenever the convar is changed.
----@field initializer? fun():any # Optional initializer function which will set the default value on player spawn.
----@field persistent boolean # If the value is saved to player on change.
----@field wasChangedByUser boolean # Whether the value was changed by the user.
----@field displayFunc? fun(val:any) # The function called when the convar is called without any parameters. By default it just prints the value.
----@field defaultValue? string # The default value of the convar given by the registration. Not the value set in cfg or launch options.
+---@field desc? string # Description of the convar/command. Is displayed below the current value when called without a parameter
+---@field value string # Raw value of the convar
+---@field prevValue string # Previous raw value of the convar
+---@field callback? fun(val:string, ...):any? # Optional callback function whenever the convar is changed
+---@field initializer? fun():any # Optional initializer function which will set the default value on player spawn
+---@field persistent boolean # If the value is saved to player on change
+---@field wasChangedByUser boolean # Whether the value was changed by the user
+---@field displayFunc? fun(val:any) # The function called when the convar is called without any parameters. By default it just prints the value
+---@field defaultValue? string # The default value of the convar given by the registration. Not the value set in cfg or launch options
+---
---Types of convars.
+---
---@alias EasyConvarsType
---| `EASYCONVARS_CONVAR`
---| `EASYCONVARS_COMMAND`
@@ -54,6 +58,7 @@ EasyConvars.registered = {}
EasyConvars._isLoading = true
---The function called after all convars have been initialized.
+---@deprecated
local postInitializer = nil
---Converts any value to "1" or "0" depending on whether it represents true or false.
@@ -175,10 +180,10 @@ end, nil)
--- - [EasyConvars:RegisterToggle](lua://EasyConvars.RegisterToggle)
--- - [EasyConvars:RegisterCommand](lua://EasyConvars.RegisterCommand)
---
----@param ctype EasyConvarsType # Type of the convar.
+---@param ctype EasyConvarsType # Type of the convar
---@param name string # Name of the convar
----@param defaultValue any|fun():any # Will be converted to a string. If given a function, the value will be determined on player spawn.
----@param onUpdate? (fun(val:string, ...):any?)|(fun(newVal:string, oldVal:string):any) # Optional callback function.
+---@param defaultValue any|fun():any # Will be converted to a string. If given a function, the value will be determined on player spawn
+---@param onUpdate? (fun(val:string, ...):any?)|(fun(newVal:string, oldVal:string):any) # Optional callback function
---@param helpText? string # Description of the convar
---@param flags? CVarFlags|integer # Flag for the convar
---@return EasyConvarsRegisteredData # The new registered cvar data
@@ -257,7 +262,7 @@ end
---
---This is done automatically when the value is changed if SetPersistent is set to true.
---
----@param name string # The name of the convar to save.
+---@param name string # The name of the convar to save
function EasyConvars:Save(name)
if not self.registered[name] then return end
@@ -277,8 +282,8 @@ end
---
---This is done automatically when the player spawns for any previously saved convar.
---
----@param name string # The name of the convar to load.
----@return boolean # Returns true if the convar was loaded successfully.
+---@param name string # The name of the convar to load
+---@return boolean # `true` if the convar was loaded successfully
function EasyConvars:Load(name)
local cvar = self.registered[name]
if not cvar then return false end
@@ -307,8 +312,8 @@ end
---
---It will be saved to the player when changed and load its previous state when the player spawns.
---
----@param name string # The name of the convar.
----@param persistent boolean # Whether the convar should be persistent.
+---@param name string # The name of the convar
+---@param persistent boolean # `true` if the convar should be persistent
function EasyConvars:SetPersistent(name, persistent)
if not self.registered[name] then return end
self.registered[name].persistent = persistent
@@ -385,7 +390,7 @@ end
---Returns the convar as a string.
---
---@param name string # Name of the convar
----@return string? # The value of the convar as a string or nil if convar does not exist
+---@return string? # The value of the convar as a string or `nil` if convar does not exist
function EasyConvars:GetStr(name)
local cvar = self.registered[name]
if not cvar then return nil end
@@ -401,7 +406,7 @@ end
---Returns the convar as a boolean.
---
---@param name string # Name of the convar
----@return boolean? # The value of the convar as a boolean or nil if convar does not exist
+---@return boolean? # The value of the convar as a boolean or `nil` if convar does not exist
function EasyConvars:GetBool(name)
if not self.registered[name] then return nil end
return truthy(self:GetStr(name))
@@ -411,7 +416,7 @@ end
---Returns the convar as a float.
---
---@param name string # Name of the convar
----@return number? # The value of the convar as a number or nil if convar does not exist
+---@return number? # The value of the convar as a number or `nil` if convar does not exist
function EasyConvars:GetFloat(name)
if not self.registered[name] then return nil end
return tonumber(self:GetStr(name)) or 0
@@ -421,7 +426,7 @@ end
---Returns the convar as an integer.
---
---@param name string # Name of the convar
----@return integer? # The value of the convar as a truncated number or nil if convar does not exist
+---@return integer? # The value of the convar as a truncated number or `nil` if convar does not exist
function EasyConvars:GetInt(name)
if not self.registered[name] then return nil end
return math.floor(self:GetFloat(name))
@@ -495,11 +500,11 @@ end
---
---Forces the convar to be considered changed by the user.
---
----This can be useful if you want to stop a convar from initializing because you're setting its value early.
----Or if you're setting the value by some unusual means for the user.
+---This can be useful if you want to stop a convar from initializing because you're setting its value early
+---or if you're setting the value by some unusual means for the user.
---
----@param name string
----@param wasChanged boolean
+---@param name string # Name of the convar
+---@param wasChanged boolean # `true` to mark the convar as changed by the user
function EasyConvars:SetWasChanged(name, wasChanged)
local reg = self.registered[name]
if not reg then return end
@@ -509,8 +514,10 @@ end
---
---Sets the value of the convar if it hasn't been changed by the user.
---
+---@see EasyConvars.WasChangedByUser
+---@see EasyConvars.SetWasChanged
---@param name string
----@param value any # The value to set. Will be converted to a string representation.
+---@param value any # The value to set - will be converted to a string representation
function EasyConvars:SetIfUnchanged(name, value)
local reg = self.registered[name]
if not reg then return end
@@ -544,11 +551,9 @@ function EasyConvars:SetPostInitializer(func)
end
--- Check which version of listener is available
-local listener = ListenToPlayerEvent
-if listener == nil then
- listener = ListenToGameEvent
-end
+-- Use best available listener
+local listener = ListenToPlayerEvent or ListenToGameEvent
+
listener("player_activate", function (params)
Player:Delay(function ()
diff --git a/scripts/vscripts/alyxlib/math/common.lua b/scripts/vscripts/alyxlib/math/common.lua
index 7f6a817..c4c0d86 100644
--- a/scripts/vscripts/alyxlib/math/common.lua
+++ b/scripts/vscripts/alyxlib/math/common.lua
@@ -4,7 +4,7 @@
Extends the math library with useful functions.
- If not using `vscripts/alyxlib/init.lua`, load this file at game start using the following line:
+ If not using `alyxlib/init.lua`, load this file at game start using the following line:
require "alyxlib.math.common"
]]
@@ -12,10 +12,10 @@
local version = "v1.3.0"
---
----Get the sign of a number.
+---Gets the sign of a number.
---
----@param x number # The input number.
----@return 1|0|-1 # Returns 1 if the number is positive, -1 if the number is negative, or 0 if the number is zero.
+---@param x number # The input number
+---@return -1|0|1 # Sign of the number: -1 for negative, 1 for positive, 0 if zero
function math.sign(x)
return x > 0 and 1 or (x < 0 and -1 or 0)
end
@@ -38,11 +38,11 @@ function math.trunc(number, places)
end
---
----Rounds a number to the specified number of decimal places.
+---Rounds a number to a given number of decimal places.
---
----@param number number # The input number to be rounded.
----@param decimals? integer # The number of decimal places to round to. If not provided, the number will be rounded to the nearest whole number.
----@return number # The input number rounded to the specified decimal places or nearest whole number.
+---@param number number # The number to be rounded
+---@param decimals? integer # Decimal places to round to (default: 0)
+---@return number # The rounded number
function math.round(number, decimals)
local shift = 10 ^ (decimals or 0)
return math.floor(number * shift + 0.5) / shift
@@ -51,11 +51,11 @@ end
---
---Checks if two numbers are close to each other within a specified tolerance.
---
----@param a number # The first number to compare.
----@param b number # The second number to compare.
----@param rel_tol? number # The relative tolerance (optional). Defines the maximum allowed relative difference between `a` and `b` as a percentage of the larger of the two values.
----@param abs_tol? number # The absolute tolerance (optional). Defines the maximum allowed fixed difference between `a` and `b`, regardless of their magnitudes.
----@return boolean # Returns `true` if the numbers are considered close based on the specified tolerances; otherwise, returns `false`.
+---@param a number # The first number to compare
+---@param b number # The second number to compare
+---@param rel_tol? number # Defines the maximum allowed relative difference between `a` and `b` as a percentage of the larger of the two values
+---@param abs_tol? number # Defines the maximum allowed fixed difference between `a` and `b`, regardless of their magnitudes
+---@return boolean # `true` if the numbers are considered close based on the specified tolerances, `false` otherwise
---
--- **Examples:**
---
@@ -88,8 +88,8 @@ end
---
---Checks if a given number has a fractional part (decimal part).
---
----@param number number # The number to check for fractional part.
----@return boolean # True if the number has a fractional part, false otherwise.
+---@param number number # The number to check
+---@return boolean # `true` if the number has a fractional part, `false` otherwise.
function math.has_frac(number)
return type(number) == "number" and number ~= math.floor(number)
end
@@ -97,8 +97,8 @@ end
---
---Returns the fractional part of a number.
---
----@param number number # The number to get the fractional part of.
----@return number # The fractional part of the number.
+---@param number number # The number to get the fractional part of
+---@return number # The fractional part of the number
function math.get_frac(number)
return number - math.floor(number)
end
diff --git a/scripts/vscripts/alyxlib/math/obb.lua b/scripts/vscripts/alyxlib/math/obb.lua
index 0a194b7..aea7484 100644
--- a/scripts/vscripts/alyxlib/math/obb.lua
+++ b/scripts/vscripts/alyxlib/math/obb.lua
@@ -4,7 +4,7 @@
Functions for drawing and testing OBB intersections.
- If not using `vscripts/alyxlib/init.lua`, load this file at game start using the following line:
+ If not using `alyxlib/init.lua`, load this file at game start using the following line:
require "alyxlib.math.obb"
]]
@@ -17,11 +17,11 @@ local insert = table.insert
---
---Projects an OBB onto an axis.
---
----@param centerLocal Vector # The center of the OBB in local space.
----@param half Vector # The half extents of the OBB in local space.
----@param origin Vector # The origin of the OBB in world space.
----@param angles QAngle # The angles of the OBB in world space.
----@param axis Vector # The axis to project on.
+---@param centerLocal Vector # The center of the OBB in local space
+---@param half Vector # The half extents of the OBB in local space
+---@param origin Vector # The origin of the OBB in world space
+---@param angles QAngle # The angles of the OBB in world space
+---@param axis Vector # The axis to project on
local function projectOBB(centerLocal, half, origin, angles, axis)
local f, r, u = angles:Forward(), angles:Left(), angles:Up()
local centerWorld = origin + f*centerLocal.x + r*centerLocal.y + u*centerLocal.z
@@ -35,15 +35,15 @@ end
---OBB data table for use with obb.lua functions.
---
---@class OBBData
----@field center Vector # The center of the OBB in local space.
----@field half Vector # The half extents of the OBB in local space.
+---@field center Vector # The center of the OBB in local space
+---@field half Vector # The half extents of the OBB in local space
---
---Returns the center and half extents of an OBB in local space.
---
----@param mins Vector # The local space minimum corner.
----@param maxs Vector # The local space maximum corner.
----@return OBBData # The OBB data.
+---@param mins Vector # The local space minimum corner
+---@param maxs Vector # The local space maximum corner
+---@return OBBData # The OBB data
function GetBoundingOBBData(mins, maxs)
local center = (mins + maxs) * 0.5
local half = (maxs - mins) * 0.5
@@ -56,8 +56,8 @@ end
---
---Returns the center and half extents of an entity's OBB in local space.
---
----@param entity EntityHandle # The entity.
----@return OBBData # The OBB data.
+---@param entity EntityHandle # The entity to get the OBB data for
+---@return OBBData # The OBB data
function GetEntityOBBData(entity)
local mins = entity:GetBoundingMins()
local maxs = entity:GetBoundingMaxs()
@@ -67,9 +67,9 @@ end
---
---Returns the world space minimum and maximum corners of an entity's OBB.
---
----@param entity EntityHandle # The entity.
----@return Vector # The world space minimum corner.
----@return Vector # The world space maximum corner.
+---@param entity EntityHandle # The entity to get the AABB for
+---@return Vector # The world space minimum corner
+---@return Vector # The world space maximum corner
function GetEntityAABB(entity)
local data = GetEntityOBBData(entity)
local origin = entity:GetOrigin()
@@ -104,11 +104,11 @@ end
---
---Tests if two AABBs intersect.
---
----@param aMin Vector # The minimum corner of the first AABB.
----@param aMax Vector # The maximum corner of the first AABB.
----@param bMin Vector # The minimum corner of the second AABB.
----@param bMax Vector # The maximum corner of the second AABB.
----@return boolean # True if the AABBs intersect.
+---@param aMin Vector # The minimum corner of the first AABB
+---@param aMax Vector # The maximum corner of the first AABB
+---@param bMin Vector # The minimum corner of the second AABB
+---@param bMax Vector # The maximum corner of the second AABB
+---@return boolean # `true` if the AABBs intersect, `false` otherwise
function AABBvsAABB(aMin, aMax, bMin, bMax)
return aMin.x <= bMax.x and aMax.x >= bMin.x and
aMin.y <= bMax.y and aMax.y >= bMin.y and
@@ -118,13 +118,13 @@ end
---
---Tests if two OBBs intersect.
---
----@param obbDataA OBBData # The data of the first OBB.
----@param originA Vector # The world space origin of the first OBB.
----@param anglesA QAngle # The angles of the first OBB.
----@param obbDataB OBBData # The data of the second OBB.
----@param originB Vector # The world space origin of the second OBB.
----@param anglesB QAngle # The angles of the second OBB.
----@return boolean # True if the OBBs intersect.
+---@param obbDataA OBBData # The data of the first OBB
+---@param originA Vector # The world space origin of the first OBB
+---@param anglesA QAngle # The angles of the first OBB
+---@param obbDataB OBBData # The data of the second OBB
+---@param originB Vector # The world space origin of the second OBB
+---@param anglesB QAngle # The angles of the second OBB
+---@return boolean # `true` if the OBBs intersect, `false` otherwise
function OBBvsOBB(obbDataA, originA, anglesA, obbDataB, originB, anglesB)
local fA, rA, uA = anglesA:Forward(), anglesA:Left(), anglesA:Up()
local fB, rB, uB = anglesB:Forward(), anglesB:Left(), anglesB:Up()
@@ -154,12 +154,12 @@ end
---
---Tests if an AABB and an OBB intersect.
---
----@param aabbMin Vector # The minimum corner of the AABB.
----@param aabbMax Vector # The maximum corner of the AABB.
----@param obbData OBBData # The data of the OBB.
----@param obbOrigin Vector # The world space origin of the OBB.
----@param obbAngles QAngle # The angles of the OBB.
----@return boolean # True if the AABB and OBB intersect.
+---@param aabbMin Vector # The minimum corner of the AABB
+---@param aabbMax Vector # The maximum corner of the AABB
+---@param obbData OBBData # The data of the OBB
+---@param obbOrigin Vector # The world space origin of the OBB
+---@param obbAngles QAngle # The angles of the OBB
+---@return boolean # `true` if the AABB and OBB intersect, `false` otherwise
function AABBvsOBB(aabbMin, aabbMax, obbData, obbOrigin, obbAngles)
local aCenter = (aabbMin + aabbMax) * 0.5
@@ -179,10 +179,10 @@ end
---
---Tests if a point is inside an AABB.
---
----@param aMin Vector # The minimum corner of the AABB.
----@param aMax Vector # The maximum corner of the AABB.
----@param point Vector # The point to test.
----@return boolean
+---@param aMin Vector # The minimum corner of the AABB
+---@param aMax Vector # The maximum corner of the AABB
+---@param point Vector # The point to test
+---@return boolean # `true` if the point is inside the AABB, `false` otherwise
function PointInAABB(aMin, aMax, point)
return point.x >= aMin.x and point.x <= aMax.x and
point.y >= aMin.y and point.y <= aMax.y and
@@ -192,11 +192,11 @@ end
---
---Tests if a point is inside an OBB.
---
----@param obbData OBBData # The OBB data (center, half extents in local space).
----@param origin Vector # The world space origin of the OBB.
----@param angles QAngle # The world space orientation of the OBB.
----@param point Vector # The point to test.
----@return boolean
+---@param obbData OBBData # The OBB data (center, half extents in local space)
+---@param origin Vector # The world space origin of the OBB
+---@param angles QAngle # The world space orientation of the OBB
+---@param point Vector # The point to test
+---@return boolean # `true` if the point is inside the OBB, `false` otherwise
function PointInOBB(obbData, origin, angles, point)
local f, r, u = angles:Forward(), angles:Left(), angles:Up()
@@ -222,12 +222,12 @@ end
---
---Draws an OBB in the world.
---
----@param obbData OBBData # The data of the OBB.
----@param origin Vector # The world space origin of the OBB.
----@param angles QAngle # The angles of the OBB.
----@param color Vector # The color of the OBB.
----@param noDepthTest boolean # True if the OBB should be drawn above all geometry.
----@param seconds number # The number of seconds the OBB should be visible for.
+---@param obbData OBBData # The data of the OBB
+---@param origin Vector # The world space origin of the OBB
+---@param angles QAngle # The angles of the OBB
+---@param color Vector # The color of the OBB
+---@param noDepthTest boolean # True if the OBB should be drawn above all geometry
+---@param seconds number # The number of seconds the OBB should be visible for
function DebugDrawOBB(obbData, origin, angles, color, noDepthTest, seconds)
local f = angles:Forward()
@@ -273,10 +273,10 @@ end
---
---Draws an entity's OBB in the world.
---
----@param entity EntityHandle # The entity to draw the OBB for.
----@param color Vector # The color of the OBB in RGB.
----@param noDepthTest boolean # True if the OBB should be drawn above all geometry.
----@param seconds number # The number of seconds the OBB should be visible for.
+---@param entity EntityHandle # The entity to draw the OBB for
+---@param color Vector # The color of the OBB in RGB
+---@param noDepthTest boolean # True if the OBB should be drawn above all geometry
+---@param seconds number # The number of seconds the OBB should be visible for
function DebugDrawEntityOBB(entity, color, noDepthTest, seconds)
local obbData = GetEntityOBBData(entity)
DebugDrawOBB(obbData, entity:GetOrigin(), entity:GetAngles(), color, noDepthTest, seconds)
@@ -287,10 +287,10 @@ end
---
---The AABB is defined by the entity's bounding mins/maxs and its current origin/angles.
---
----@param entity EntityHandle # The entity to draw the AABB for.
----@param color Vector # The color of the AABB in RGB.
----@param noDepthTest boolean # True if the AABB should be drawn above all geometry.
----@param seconds number # The number of seconds the AABB should be visible for.
+---@param entity EntityHandle # The entity to draw the AABB for
+---@param color Vector # The color of the AABB in RGB
+---@param noDepthTest boolean # True if the AABB should be drawn above all geometry
+---@param seconds number # The number of seconds the AABB should be visible for
function DebugDrawEntityAABB(entity, color, noDepthTest, seconds)
local mins, maxs = GetEntityAABB(entity)
debugoverlay:Box(mins, maxs, color.x, color.y, color.z, 255, noDepthTest or false, seconds or 0)
diff --git a/scripts/vscripts/alyxlib/math/weighted_random.lua b/scripts/vscripts/alyxlib/math/weighted_random.lua
index 5f7cc32..95345b1 100644
--- a/scripts/vscripts/alyxlib/math/weighted_random.lua
+++ b/scripts/vscripts/alyxlib/math/weighted_random.lua
@@ -4,7 +4,7 @@
Weighted random allows you to assign chances to tables keys.
- If not using `vscripts/alyxlib/init.lua`, load this file at game start using the following line:
+ If not using `alyxlib/init.lua`, load this file at game start using the following line:
require "alyxlib.math.weighted_random"
]]
@@ -68,13 +68,13 @@ if pcall(require, "alyxlib.storage") then
CBaseEntity.SaveWeightedRandom = Storage.SaveWeightedRandom
---
- ---Load a WeightedRandom.
+ ---Loads a WeightedRandom.
---
---@generic T
- ---@param handle EntityHandle # Entity to load from.
- ---@param name string # Name the WeightedRandom was saved as.
- ---@param default? T # Optional default value.
- ---@return WeightedRandom|T
+ ---@param handle EntityHandle # Entity to load from
+ ---@param name string # Name the WeightedRandom was saved as
+ ---@param default? T # Optional default value
+ ---@return WeightedRandom|T # The loaded WeightedRandom
---@luadoc-ignore
Storage.LoadWeightedRandom = function(handle, name, default)
local wr = WR.__load(handle, name)
@@ -87,23 +87,23 @@ if pcall(require, "alyxlib.storage") then
end
---
----Add a table value with an associated weight.
+---Adds a table value with an associated weight.
---
---If `tbl` already has a weight key then `weight` parameter can be omitted.
---
----**Note:** The table `tbl` is not cloned, the given reference is used.
+---**Note:** The table `tbl` is not cloned, the given reference is inserted into the list.
---
----@param tbl table # Table of values that will be returned.
----@param weight? number # Weight for this table.
+---@param tbl table # Table of values that will be returned
+---@param weight? number # Weight for this table
function WR:Add(tbl, weight)
if weight ~= nil then tbl.weight = weight end
self.ItemPool[#self.ItemPool+1] = tbl
end
---
----Get the sum of all weights in the WeightedRandom.
+---Gets the sum of all weights in the WeightedRandom.
---
----@return number # The sum of all weights.
+---@return number # The sum of all weights
function WR:TotalWeight()
local weight_sum = 0
for _,item in ipairs(self.ItemPool) do
@@ -115,9 +115,9 @@ function WR:TotalWeight()
end
---
----Pick a random table from the list of weighted tables.
+---Picks a random table from the list of weighted tables.
---
----@return WeightedRandomItem|table # The chosen table.
+---@return WeightedRandomItem|table # The randomly selected table
function WR:Random()
local weight_sum = self:TotalWeight()
local weight_remaining
@@ -139,7 +139,7 @@ function WR:Random()
end
---
----Create a new WeightedRandom object with given weights.
+---Creates a new WeightedRandom instance with given weights.
---
---E.g.
---
@@ -151,10 +151,8 @@ end
--- { weight = 0.1, name = "Extremely rare" },
--- })
---
----Params:
----
----@param weights WeightedRandomItem[] # List of weighted tables.
----@return WeightedRandom # WeightedRandom object.
+---@param weights WeightedRandomItem[] # List of weighted tables
+---@return WeightedRandom # The new WeightedRandom instance
function WeightedRandom(weights)
return setmetatable({ItemPool = weights or {}}, WR)
end
diff --git a/scripts/vscripts/alyxlib/panorama/core.lua b/scripts/vscripts/alyxlib/panorama/core.lua
index fb179c7..2acc47f 100644
--- a/scripts/vscripts/alyxlib/panorama/core.lua
+++ b/scripts/vscripts/alyxlib/panorama/core.lua
@@ -3,6 +3,10 @@
https://github.com/FrostSource/alyxlib
Panorama core library.
+
+ If not using `alyxlib/init.lua`, load this file at game start using the following line:
+
+ require "alyxlib.panorama.core"
]]
Panorama = {}
@@ -18,13 +22,13 @@ local function FilterText(text)
end
---
----Initializes a panorama panel with a unique id.
+---Initializes a panorama panel with a unique id.
---This must be done every time the entity loads, even on save game loads.
---
---The entity can be initialized if it accepts the input "AddCSSClass".
---
----@param panelEntity EntityHandle # The entity to initialize.
----@param customId? string # If nil the id will be generated automatically.
+---@param panelEntity EntityHandle # The entity to initialize
+---@param customId? string # The custom id to use, otherwise a unique id will be generated
function Panorama:InitPanel(panelEntity, customId)
local id = customId
@@ -57,10 +61,10 @@ local function flattenOrderedTable(tbl, out)
end
---
----Send data to a panorama panel.
+---Sends data to a panorama panel.
---
----@param panelEntity EntityHandle # The entity to send data to.
----@param ... any # The data to send. Each value will be converted to a string.
+---@param panelEntity EntityHandle # The entity to send data to
+---@param ... any # The data to send - each value will be converted to a string
function Panorama:Send(panelEntity, ...)
---@diagnostic disable-next-line: undefined-field
local id = panelEntity.__panoid
@@ -138,10 +142,10 @@ function Panorama:ToJSON(value)
end
---
----Get the panorama id from an entity if it has one.
+---Gets the panorama id from an entity if it has one.
---
----@param entityPanel EntityHandle
----@return string?
+---@param entityPanel EntityHandle # The entity to get the id from
+---@return string? # The panorama id
function Panorama:GetId(entityPanel)
---@diagnostic disable-next-line: undefined-field
return entityPanel.__panoid
diff --git a/scripts/vscripts/alyxlib/player/core.lua b/scripts/vscripts/alyxlib/player/core.lua
index 5320744..009e648 100644
--- a/scripts/vscripts/alyxlib/player/core.lua
+++ b/scripts/vscripts/alyxlib/player/core.lua
@@ -5,7 +5,7 @@
Player script allows for more advanced player manipulation and easier
entity access for player related entities by extending the player class.
- If not using `vscripts/alyxlib/init.lua`, load this file at game start using the following line:
+ If not using `alyxlib/init.lua`, load this file at game start using the following line:
require "alyxlib.player.core"
]]
@@ -126,6 +126,7 @@ CBasePlayer.Items = {
---Ammo for the shotgun. This is number of shells.
shotgun = 0,
---Ammo for the generic pistol. This is number of magazines, not bullets.
+ ---@TODO Try to track for individual generic pistols
generic_pistol = 0,
},
@@ -190,7 +191,9 @@ CPropVRHand.Opposite = nil
-- Class extension functions --
-------------------------------
----Force the player to drop an entity if held.
+---
+---Forces the player to drop an entity if held.
+---
---@param handle EntityHandle # Handle of the entity to drop
function CBasePlayer:DropByHandle(handle)
if IsValidEntity(handle) then
@@ -200,47 +203,61 @@ function CBasePlayer:DropByHandle(handle)
end
end
----Force the player to drop any item held in their left hand.
+---
+---Forces the player to drop any item held in their left hand.
+---
function CBasePlayer:DropLeftHand()
self:DropByHandle(self.LeftHand.ItemHeld)
end
Expose(CBasePlayer.DropLeftHand, "DropLeftHand", CBasePlayer)
----Force the player to drop any item held in their right hand.
+---
+---Forces the player to drop any item held in their right hand.
+---
function CBasePlayer:DropRightHand()
self:DropByHandle(self.RightHand.ItemHeld)
end
Expose(CBasePlayer.DropRightHand, "DropRightHand", CBasePlayer)
----Force the player to drop any item held in their primary hand.
+---
+---Forces the player to drop any item held in their primary hand.
+---
function CBasePlayer:DropPrimaryHand()
self:DropByHandle(self.PrimaryHand.ItemHeld)
end
Expose(CBasePlayer.DropPrimaryHand, "DropPrimaryHand", CBasePlayer)
----Force the player to drop any item held in their secondary/off hand.
+---
+---Forces the player to drop any item held in their secondary/off hand.
+---
function CBasePlayer:DropSecondaryHand()
self:DropByHandle(self.SecondaryHand.ItemHeld)
end
Expose(CBasePlayer.DropSecondaryHand, "DropSecondaryHand", CBasePlayer)
----Force the player to drop the caller entity if held.
----@param data IOParams
+---
+---Forces the player to drop the caller entity if held.
+---
+---@param data IOParams # The IOParams table
function CBasePlayer:DropCaller(data)
self:DropByHandle(data.caller)
end
Expose(CBasePlayer.DropCaller, "DropCaller", CBasePlayer)
----Force the player to drop the activator entity if held.
----@param data IOParams
+---
+---Forces the player to drop the activator entity if held.
+---
+---@param data IOParams # The IOParams table
function CBasePlayer:DropActivator(data)
self:DropByHandle(data.activator)
end
Expose(CBasePlayer.DropActivator, "DropActivator", CBasePlayer)
----Force the player to grab `handle` with `hand`.
----@param handle EntityHandle
----@param hand? CPropVRHand|0|1
+---
+---Forces the player to grab `handle` with `hand`.
+---
+---@param handle EntityHandle # Handle of the entity to grab
+---@param hand? CPropVRHand|0|1 # Hand to grab with
function CBasePlayer:GrabByHandle(handle, hand)
if IsEntity(handle, true) then
if type(hand) ~= "number" then
@@ -260,20 +277,27 @@ function CBasePlayer:GrabByHandle(handle, hand)
end
end
+---
---Force the player to grab the caller entity.
----@param data IOParams
+---
+---@param data IOParams # The IOParams table
function CBasePlayer:GrabCaller(data)
self:GrabByHandle(data.caller)
end
Expose(CBasePlayer.GrabCaller, "GrabCaller", CBasePlayer)
+---
---Force the player to grab the activator entity.
----@param data IOParams
+---
+---@param data IOParams # The IOParams table
function CBasePlayer:GrabActivator(data)
self:GrabByHandle(data.activator)
end
Expose(CBasePlayer.GrabActivator, "GrabActivator", CBasePlayer)
+---
+---Movement types for the VR player.
+---
---@enum PlayerMoveType
PlayerMoveType = {
TeleportBlink = 0,
@@ -316,8 +340,9 @@ end
---
---Returns the entity the player is looking at directly.
----@param maxDistance? number # Max distance the trace can search.
----@return EntityHandle?
+---
+---@param maxDistance? number # Max distance the trace can search
+---@return EntityHandle? # The entity the player is looking at
function CBasePlayer:GetLookingAt(maxDistance)
maxDistance = maxDistance or 2048
---@type TraceTableLine
@@ -332,9 +357,11 @@ function CBasePlayer:GetLookingAt(maxDistance)
return nil
end
+---
---Disables fall damage for the player.
----@TODO: Change to entity save.
+---
function CBasePlayer:DisableFallDamage()
+ ---@TODO: Consider changing to SaveEntity.
local name = Storage.LoadString(self, "FallDamageFilterName", DoUniqueString("__player_fall_damage_filter"))
Storage.SaveString(self, "FallDamageFilterName", name)
local filter = Entities:FindByName(nil, name) or SpawnEntityFromTableSynchronous("filter_damage_type",{
@@ -345,7 +372,9 @@ function CBasePlayer:DisableFallDamage()
end
Expose(CBasePlayer.DisableFallDamage, "DisableFallDamage", CBasePlayer)
+---
---Enables fall damage for the player.
+---
function CBasePlayer:EnableFallDamage()
--Killing the filter is not necessary but may be helpful.
local name = Storage.LoadString(self, "FallDamageFilterName", "")
@@ -357,11 +386,13 @@ function CBasePlayer:EnableFallDamage()
end
Expose(CBasePlayer.EnableFallDamage, "EnableFallDamage", CBasePlayer)
+---
---Adds resources to the player.
----@param pistol_ammo? number
----@param rapidfire_ammo? number
----@param shotgun_ammo? number
----@param resin? number
+---
+---@param pistol_ammo? number # Amount of pistol ammo
+---@param rapidfire_ammo? number # Amount of rapidfire ammo
+---@param shotgun_ammo? number # Amount of shotgun ammo
+---@param resin? number # Amount of resin
function CBasePlayer:AddResources(pistol_ammo, rapidfire_ammo, shotgun_ammo, resin)
pistol_ammo = pistol_ammo or 0
rapidfire_ammo = rapidfire_ammo or 0
@@ -377,11 +408,15 @@ function CBasePlayer:AddResources(pistol_ammo, rapidfire_ammo, shotgun_ammo, res
)
end
+---
---Sets resources for the player.
----@param pistol_ammo? number
----@param rapidfire_ammo? number
----@param shotgun_ammo? number
----@param resin? number
+---
+---**This might give inaccurate amounts for omitted values.**
+---
+---@param pistol_ammo? number # Amount of pistol ammo
+---@param rapidfire_ammo? number # Amount of rapidfire ammo
+---@param shotgun_ammo? number # Amount of shotgun ammo
+---@param resin? number # Amount of resin
function CBasePlayer:SetResources(pistol_ammo, rapidfire_ammo, shotgun_ammo, resin)
-- Number value is different in-game (setresources uses bullets)
SendToServerConsole("hlvr_setresources "..
@@ -399,14 +434,18 @@ function CBasePlayer:SetResources(pistol_ammo, rapidfire_ammo, shotgun_ammo, res
)
end
----Set the items that player has manually.
----This is purely for scripting and does not modify the actual items the player has in game.
----Use `Player:AddResources` to modify in-game items.
----@param energygun_ammo? integer
----@param generic_pistol_ammo? integer
----@param rapidfire_ammo? integer
----@param shotgun_ammo? integer
----@param resin? integer
+---
+---Manually sets the items that player has.
+---
+---**This is purely for scripting and does not modify the actual items the player has in-game.**
+---
+---Use [Player:AddResources](lua://CBasePlayer.AddResources) to modify in-game items.
+---
+---@param energygun_ammo? integer # Amount of pistol ammo
+---@param generic_pistol_ammo? integer # Amount of generic pistol ammo
+---@param rapidfire_ammo? integer # Amount of rapidfire ammo
+---@param shotgun_ammo? integer # Amount of shotgun ammo
+---@param resin? integer # Amount of resin
function CBasePlayer:SetItems(
energygun_ammo,
generic_pistol_ammo,
@@ -426,30 +465,30 @@ end
---
----Add pistol ammo to the player.
+---Adds pistol ammo to the player.
---
----@param amount number
+---@param amount number # Amount of pistol ammo
function CBasePlayer:AddPistolAmmo(amount)
self:AddResources(amount, nil, nil, nil)
end
---
----Add shotgun ammo to the player.
+---Adds shotgun ammo to the player.
---
----@param amount number
+---@param amount number # Amount of shotgun ammo
function CBasePlayer:AddShotgunAmmo(amount)
self:AddResources(nil, nil, amount, nil)
end
---
----Add rapidfire ammo to the player.
+---Adds rapidfire ammo to the player.
---
----@param amount number
+---@param amount number # Amount of rapidfire ammo
function CBasePlayer:AddRapidfireAmmo(amount)
self:AddResources(nil, amount, nil, nil)
end
---
----Add resin to the player.
+---Adds resin to the player.
---
----@param amount number
+---@param amount number # Amount of resin
function CBasePlayer:AddResin(amount)
self:AddResources(nil, nil, nil, amount)
end
@@ -457,7 +496,7 @@ end
---
---Gets the items currently held or in wrist pockets.
---
----@return EntityHandle[]
+---@return EntityHandle[] # List of items
function CBasePlayer:GetImmediateItems()
return {
self.LeftHand.WristItem,
@@ -468,11 +507,9 @@ function CBasePlayer:GetImmediateItems()
end
---
----Gets the grenades currently held or in wrist pockets.
----
----Use `#Player:GetGrenades()` to get number of grenades player has access to.
+---Gets the grenades currently held and in wrist pockets.
---
----@return EntityHandle[]
+---@return EntityHandle[] # List of grenades
function CBasePlayer:GetGrenades()
local grenades = {}
local immediate_items = self:GetImmediateItems()
@@ -485,9 +522,7 @@ function CBasePlayer:GetGrenades()
end
---
----Gets the health pens currently held or in wrist pockets.
----
----Use `#Player:GetHealthPens()` to get number of health pens player has access to.
+---Gets the health pens currently held and in wrist pockets.
---
---@return EntityHandle[]
function CBasePlayer:GetHealthPens()
@@ -504,9 +539,9 @@ end
---
---Marges an existing prop with a given hand.
---
----@param hand CPropVRHand|0|1 # The hand handle or index.
----@param prop EntityHandle|string # The prop handle or targetname.
----@param hide_hand boolean # If the hand should turn invisible after merging.
+---@param hand CPropVRHand|0|1 # The hand handle or ID
+---@param prop EntityHandle|string # The prop handle or targetname
+---@param hide_hand boolean # If the hand should turn invisible after merging
function CBasePlayer:MergePropWithHand(hand, prop, hide_hand)
if type(hand) == "number" then
hand = self.Hands[hand+1]
@@ -515,9 +550,10 @@ function CBasePlayer:MergePropWithHand(hand, prop, hide_hand)
end
---
----Return if the player has a gun equipped.
+---Checks if the player has a gun equipped.
+---`hlvr_multitool` is not considered a "gun"
---
----@return boolean
+---@return boolean # `true` if the player has a gun equipped
function CBasePlayer:HasWeaponEquipped()
return self.CurrentlyEquipped == PLAYER_WEAPON_ENERGYGUN
or self.CurrentlyEquipped == PLAYER_WEAPON_SHOTGUN
@@ -528,9 +564,9 @@ end
---
---Get the amount of ammo stored in the backpack for the currently equipped weapon.
---
----This is not accurate if ammo was given through special means like info_hlvr_equip_player.
+---**This is not accurate if ammo was given through special means like `info_hlvr_equip_player`.**
---
----@return number # The amount of ammo, or 0 if no weapon equipped
+---@return number # The amount of ammo, or `0` if no weapon equipped
function CBasePlayer:GetCurrentWeaponReserves()
local currentlyEquipped = self.CurrentlyEquipped
if currentlyEquipped == PLAYER_WEAPON_ENERGYGUN then
@@ -545,8 +581,10 @@ function CBasePlayer:GetCurrentWeaponReserves()
return 0
end
----Player has item holder equipped.
----@return boolean
+---
+---Checks if the player has an item (wrist) holder equipped.
+---
+---@return boolean # `true` if the player has an item holder
function CBasePlayer:HasItemHolder()
for _, hand in ipairs(self.Hands) do
if hand:GetFirstChildWithClassname("hlvr_hand_item_holder") then
@@ -560,8 +598,10 @@ function CBasePlayer:HasItemHolder()
return false
end
----Player has grabbity gloves equipped.
----@return boolean
+---
+---Checks if the player has grabbity gloves equipped.
+---
+---@return boolean # `true` if the player has grabbity gloves
function CBasePlayer:HasGrabbityGloves()
return self.PrimaryHand:GetGrabbityGlove() ~= nil
end
@@ -570,11 +610,14 @@ function CBasePlayer:GetFlashlight()
return self.SecondaryHand:GetFirstChildWithClassname("hlvr_flashlight_attachment")
end
----Get the first entity the flashlight is pointed at (if the flashlight exists).
+---
+---Gets the first entity the flashlight is pointed at.
+---
---If flashlight does not exist, both returns will be `nil`.
----@param maxDistance number # Max tracing distance, default is 2048.
----@return EntityHandle|nil # The entity that was hit, or nil.
----@return Vector|nil # The position the trace hit, regardless of entity found.
+---
+---@param maxDistance? number # Max tracing distance (default: 2048)
+---@return EntityHandle|nil # The entity that was hit
+---@return Vector|nil # The position the trace hit, regardless of entity found
function CBasePlayer:GetFlashlightPointedAt(maxDistance)
local flashlight = self:GetFlashlight()
if flashlight then
@@ -592,10 +635,14 @@ function CBasePlayer:GetFlashlightPointedAt(maxDistance)
end
end
----Gets the current resin from the player.
----This is can be more accurate than `Player.Items.resin`
----Calling this will update `Player.Items.resin`
----@return number
+---
+---Gets the current resin count the player has.
+---
+---This is can be more accurate than `Player.Items.resin`.
+---
+---Calling this will update `Player.Items.resin`.
+---
+---@return number # The current resin count
function CBasePlayer:GetResin()
local t = ({}) --[[@as CriteriaTable]]
self:GatherCriteria(t)
@@ -606,16 +653,19 @@ function CBasePlayer:GetResin()
return r
end
----Gets if player is holding an entity in either hand.
----@param entity EntityHandle
----@return boolean
+---
+---Gets if player is holding a given entity in either hand.
+---
+---@param entity EntityHandle # The entity to check
+---@return boolean # `true` if the player is holding the entity
function CBasePlayer:IsHolding(entity)
return self.PrimaryHand.ItemHeld == entity or self.SecondaryHand.ItemHeld == entity
end
----Get the entity handle of the currently equipped weapon/item.
----If nothing is equipped this will return the primary hand entity.
----@return EntityHandle|nil
+---
+---Gets the entity handle of the currently equipped weapon, including `hlvr_multitool`.
+---
+---@return EntityHandle|nil # The equipped weapon, or `nil` if no weapon equipped
function CBasePlayer:GetWeapon()
if self.CurrentlyEquipped == PLAYER_WEAPON_ENERGYGUN then
return self.Items.weapons.energygun
@@ -638,11 +688,10 @@ function CBasePlayer:GetWeapon()
end
---
----Get the current upgrades for the player's pistol.
+---Gets the current upgrades for the player's pistol.
---
----@param weapon? EntityHandle # Optional weapon to check instead of the player's weapon.
----@return PlayerPistolUpgrades[]
----@overload fun(self: CBasePlayer): PlayerPistolUpgrades[]
+---@param weapon? EntityHandle # Optional weapon to check instead of the player's weapon
+---@return PlayerPistolUpgrades[] # List of upgrades
function CBasePlayer:GetPistolUpgrades(weapon)
local pistol = weapon or self.Items.weapons.energygun
@@ -668,11 +717,10 @@ function CBasePlayer:GetPistolUpgrades(weapon)
end
---
----Get the current upgrades for the player's rapidfire.
+---Gets the current upgrades for the player's rapidfire.
---
----@param weapon? EntityHandle # Optional weapon to check instead of the player's weapon.
----@return PlayerRapidfireUpgrades[]
----@overload fun(self: CBasePlayer): PlayerRapidfireUpgrades[]
+---@param weapon? EntityHandle # Optional weapon to check instead of the player's weapon
+---@return PlayerRapidfireUpgrades[] # List of upgrades
function CBasePlayer:GetRapidfireUpgrades(weapon)
local rapidfire = weapon or self.Items.weapons.rapidfire
@@ -696,13 +744,12 @@ function CBasePlayer:GetRapidfireUpgrades(weapon)
end
---
----Get the current upgrades for the player's shotgun.
+---Gets the current upgrades for the player's shotgun.
---
---**This will NOT return "shotgun_upgrade_quick_fire" because there is no known way to detect this!**
---
----@param weapon? EntityHandle # Optional weapon to check instead of the player's weapon.
----@return PlayerShotgunUpgrades[]
----@overload fun(self: CBasePlayer): PlayerShotgunUpgrades[]
+---@param weapon? EntityHandle # Optional weapon to check instead of the player's weapon
+---@return PlayerShotgunUpgrades[] # List of upgrades
function CBasePlayer:GetShotgunUpgrades(weapon)
local shotgun = weapon or self.Items.weapons.shotgun
@@ -726,10 +773,10 @@ function CBasePlayer:GetShotgunUpgrades(weapon)
end
---
----Check if the player has a specific weapon upgrade.
+---Checks if the player has a specific weapon upgrade.
---
----@param upgrade PlayerPistolUpgrades|PlayerRapidfireUpgrades|PlayerShotgunUpgrades
----@return boolean
+---@param upgrade PlayerPistolUpgrades|PlayerRapidfireUpgrades|PlayerShotgunUpgrades # Upgrade to check
+---@return boolean # `true` if the player has the upgrade
function CBasePlayer:HasWeaponUpgrade(upgrade)
if vlua.find(self:GetPistolUpgrades(), upgrade) then
return true
@@ -742,9 +789,9 @@ function CBasePlayer:HasWeaponUpgrade(upgrade)
end
---
----Get the forward vector of the player in world space coordinates (z is zeroed).
+---Gets the forward vector of the player in world space coordinates (z is zeroed).
---
----@return Vector
+---@return Vector # Forward vector
function CBasePlayer:GetWorldForward()
local f = self:GetForwardVector()
f.z = 0
@@ -759,11 +806,11 @@ local specialAttachmentsOrder = {
}
---
----Update player weapon inventory, both removing and setting.
+---Updates player weapon inventory, both removing and setting.
---
----@param removes? (string|EntityHandle)[] # List of classnames or handles to remove.
----@param set? string|EntityHandle # Classname or handle to set as active weapon.
----@return EntityHandle? # The handle of the newly set weapon if given and found.
+---@param removes? (string|EntityHandle)[] # List of classnames or handles to remove
+---@param set? string|EntityHandle # Classname or handle to set as active weapon
+---@return EntityHandle? # The handle of the newly set weapon if given and found
function CBasePlayer:UpdateWeapons(removes, set)
local hand = Player.PrimaryHand
@@ -820,9 +867,9 @@ function CBasePlayer:UpdateWeapons(removes, set)
end
---
----Remove weapons from the player inventory.
+---Removes weapons from the player inventory.
---
----@param weapons (string|EntityHandle)[] # List of classnames or handles to remove.
+---@param weapons (string|EntityHandle)[] # List of classnames or handles to remove
---@overload fun(self: CBasePlayer, weapon: string|EntityHandle)
function CBasePlayer:RemoveWeapons(weapons)
if weapons == nil or (type(weapons) == "table" and #weapons == 0) then
@@ -835,10 +882,10 @@ function CBasePlayer:RemoveWeapons(weapons)
end
---
----Set the weapon that the player is holding.
+---Sets the weapon that the player is holding.
---
----@param weapon string|EntityHandle # Classname or handle to set as active weapon.
----@return EntityHandle? # The handle of the newly set weapon if found.
+---@param weapon string|EntityHandle # Classname or handle to set as active weapon
+---@return EntityHandle? # The handle of the newly set weapon if found
function CBasePlayer:SetWeapon(weapon)
return self:UpdateWeapons(nil, weapon)
end
@@ -905,7 +952,7 @@ end
---
---Returns [Player.Items.weapons](lua://CBasePlayer.Items) flattened into a single array.
---
----@return EntityHandle[]
+---@return EntityHandle[] # List of weapon handles
function CBasePlayer:GetWeapons()
local weapons = Player.Items.weapons
@@ -919,19 +966,20 @@ function CBasePlayer:GetWeapons()
end
---
----Get the invisible player backpack.
+---Gets the invisible player backpack.
+---
---This is will return the backpack even if it has been disabled with a `info_hlvr_equip_player`.
---
----@return EntityHandle?
+---@return EntityHandle? # The backpack entity
function CBasePlayer:GetBackpack()
return Entities:FindByClassname(nil, "player_backpack")
end
---
----Enable or disable player movement. Including teleport movement.
+---Enables or disables player movement, including teleport movement.
---
----@param enabled boolean # True if movement should be enabled.
----@param delay? number # Optional delay for movement state will be changed
+---@param enabled boolean # `true` if movement should be enabled.
+---@param delay? number # Delay before movement state will be changed (default: 0)
function CBasePlayer:SetMovementEnabled(enabled, delay)
Player:EntFire("EnableTeleport", enabled and "1" or "0", delay or 0)
end
@@ -939,7 +987,7 @@ end
---
---Sets the forward vector of the HMD anchor while keeping the position the same relative to the player.
---
----Normally if the player is off-center when changing the forward vector the player may appear to move too.
+---Normally if the player is off-center from their playspace, changing the forward vector can move the player too.
---
---@param forward Vector # Normalized forward vector
function CBasePlayer:SetAnchorForwardAroundPlayer(forward)
@@ -953,9 +1001,9 @@ end
---
---Sets the angle of the HMD anchor while keeping the position the same relative to the player.
---
----Normally if the player is off-center when changing the angle the player may appear to move too.
+---Normally if the player is off-center from their playspace, changing the angle can move the player too.
---
----@param angles QAngle # New angle of the anchor
+---@param angles QAngle # New angle for the anchor
function CBasePlayer:SetAnchorAnglesAroundPlayer(angles)
local oldPos = self:GetAbsOrigin()
local relativePos = self.HMDAnchor:TransformPointWorldToEntity(oldPos)
@@ -967,9 +1015,10 @@ end
---
---Sets the origin of the HMD anchor while keeping the position the same relative to the player.
---
----This essentially moves the player by moving the anchor and can be used in instances where setting the player origin does not work.
+---This essentially moves the player by moving the anchor and can be used in instances where
+---setting the player origin does not work.
---
----@param pos Vector
+---@param pos Vector # New origin
function CBasePlayer:SetAnchorOriginAroundPlayer(pos)
self.HMDAnchor:SetAbsOrigin(pos + (self.HMDAnchor:GetAbsOrigin() - self:GetAbsOrigin()))
end
@@ -977,7 +1026,7 @@ end
---
---Sets the enabled state of the cough handpose attached to the HMD avatar.
---
----@param enabled boolean # True if the cough handpose should be enabled
+---@param enabled boolean # `true` if the cough handpose should be enabled
function CBasePlayer:SetCoughHandEnabled(enabled)
if not self.HMDAvatar then
return
diff --git a/scripts/vscripts/alyxlib/player/data.lua b/scripts/vscripts/alyxlib/player/data.lua
index 7213995..a720fc5 100644
--- a/scripts/vscripts/alyxlib/player/data.lua
+++ b/scripts/vscripts/alyxlib/player/data.lua
@@ -16,11 +16,9 @@ local version = "v1.0.0"
---
---Forces all persistent player data to save.
---
----[CBasePlayer.Items](lua://CBasePlayer.Items)\
----[CBasePlayer.CurrentlyEquipped](lua://CBasePlayer.CurrentlyEquipped)\
----[CBasePlayer.LeftHand.WristItem](lua://CPropVRHand.WristItem)\
----[CBasePlayer.RightHand.WristItem](lua://CPropVRHand.WristItem)
----
+---@see CBasePlayer.Items
+---@see CBasePlayer.CurrentlyEquipped
+---@see CPropVRHand.WristItem
local function savePlayerData()
Storage.SaveTable(Player, "PlayerItems", Player.Items)
@@ -92,7 +90,7 @@ local weaponOffsets = {
---
---@param classname string # The classname of the weapon
---@param hand CPropVRHand|number # The hand to get the offset for
----@return Vector # The default weapon offset, or `Vector()` if not found
+---@return Vector # The default weapon offset, or `(0, 0, 0)` if not found
local function GetDefaultWeaponOffset(classname, hand)
hand = hand or Player.PrimaryHand:GetHandID()
if IsEntity(hand) then
@@ -103,14 +101,16 @@ local function GetDefaultWeaponOffset(classname, hand)
end
---
----Sync the equipped weapon state of the player with the given weapon classname.
+---Syncs the equipped weapon state of the player with the given weapon classname.
---
---If no classname is given, the weapon classname will be determined from the player's criteria.
---
----@param classname? string # The classname of the weapon to sync.
----@param handle? EntityHandle # The entity handle of the weapon to sync.
----@return EntityHandle? weaponHandle # The entity handle of the weapon that was equipped.
----@return CPropVRHand? handHandle # The entity handle of the hand that the weapon was equipped to.
+---**This is used internally on weapon switch to determine equipped weapons.**
+---
+---@param classname? string # The classname of the weapon to sync
+---@param handle? EntityHandle # The entity handle of the weapon to sync
+---@return EntityHandle? weaponHandle # The entity handle of the weapon that was equipped
+---@return CPropVRHand? handHandle # The entity handle of the hand that the weapon was equipped to
local function SyncEquippedWeaponState(classname, handle)
if syncPaused then return end
@@ -228,9 +228,9 @@ local function SyncEquippedWeaponState(classname, handle)
end
---
----Restore the previously equipped weapon state.
+---Restores the previously equipped weapon state.
---
----@param fireEvent? boolean # Forces the `weapon_switch` event to be fired when restoring.
+---@param fireEvent? boolean # `true` to force-fire the `weapon_switch` event
local function RestorePreviouslyEquippedWeaponState(fireEvent)
Player.CurrentlyEquipped = previouslyEquipped
if fireEvent then
@@ -244,21 +244,21 @@ local function RestorePreviouslyEquippedWeaponState(fireEvent)
end
---
----Pause weapon state synching. This will prevent the `weapon_switch` player event from firing.
+---Pauses weapon state synching. This will prevent the `weapon_switch` player event from firing.
---
local function PauseWeaponStateSync()
syncPaused = true
end
---
----Resume weapon state synching. This will allow the `weapon_switch` player event to fire.
+---Resumes weapon state synching. This will allow the `weapon_switch` player event to fire.
---
local function ResumeWeaponStateSync()
syncPaused = false
end
---
----Check if weapon state synching is paused.
+---Checks if weapon state synching is paused.
---
local function WeaponStateSyncPaused()
return syncPaused
diff --git a/scripts/vscripts/alyxlib/player/events.lua b/scripts/vscripts/alyxlib/player/events.lua
index 1258d40..43529a3 100644
--- a/scripts/vscripts/alyxlib/player/events.lua
+++ b/scripts/vscripts/alyxlib/player/events.lua
@@ -2,7 +2,11 @@
v2.2.0
https://github.com/FrostSource/alyxlib
+ Player event management and callbacks.
+
+ If not using `alyxlib/init.lua`, load this file at game start using the following line:
+ require "alyxlib.player.events"
]]
local version = "v2.2.0"
@@ -34,10 +38,12 @@ local playerActivated = false
---@alias PLAYER_EVENTS_ALL "novr_player"|"player_activate"|"vr_player_ready"|"item_pickup"|"item_released"|"primary_hand_changed"|"player_drop_ammo_in_backpack"|"player_retrieved_backpack_clip"|"player_stored_item_in_itemholder"|"player_removed_item_from_itemholder"|"player_drop_resin_in_backpack"|"weapon_switch"
----Register a callback function with for a player event.
+---
+---Registers a callback function with a player event.
+---
---@param event PLAYER_EVENTS_ALL # Name of the event
---@param callback function # The function that will be called when the event is fired
----@param context? table # Optional: The context to pass to the function as `self`. If omitted the context will not passed to the callback.
+---@param context? table # The context to pass to first argument of `callback` - if omitted, the context will not passed to the callback
---@return integer eventID # ID used to unregister
function ListenToPlayerEvent(event, callback, context)
assert(registered_event_callbacks[event], "Unknown player event "..event)
@@ -53,8 +59,10 @@ function ListenToPlayerEvent(event, callback, context)
return registered_event_index - 1
end
----Unregisters a callback with a name.
----@param eventID integer
+---
+---Unregisters a player event callback.
+---
+---@param eventID integer # ID returned from [ListenToPlayerEvent](lua://ListenToPlayerEvent)
function StopListeningToPlayerEvent(eventID)
for _, event in pairs(registered_event_callbacks) do
event[eventID] = nil
@@ -68,11 +76,11 @@ local entityPickupEvents = {}
local entityPickupIndex = 1
---
----Listen to the pickup of a specific entity.
+---Listens to the pickup of a specific entity.
---
---@param entity EntityHandle # The entity to listen for
---@param callback function # The function that will be called when the entity is picked up
----@param context? any # Optional context passed into the callback as the first value
+---@param context? any # Context passed into the callback as the first value
---@return integer # ID used to unregister
function ListenToEntityPickup(entity, callback, context)
entityPickupEvents[entityPickupIndex] = {
@@ -85,7 +93,7 @@ function ListenToEntityPickup(entity, callback, context)
end
---
----Stop listening to an entity pickup
+---Stops listening to an entity pickup.
---
---@param eventID integer # ID returned from [ListenToEntityPickup](lua://ListenToEntityPickup)
function StopListeningToEntityPickup(eventID)
@@ -124,8 +132,8 @@ local function eventCallback(eventName, newdata)
end
---@class PlayerEventPlayerActivate : GameEventBase
----@field player CBasePlayer # The entity handle of the player.
----@field type "spawn"|"load"|"transition" # Type of player activate.
+---@field player CBasePlayer # The entity handle of the player
+---@field type "spawn"|"load"|"transition" # Type of player activate
---@class PlayerEventVRPlayerReady : PlayerEventPlayerActivate
---@field hmd_avatar CPropHMDAvatar # The hmd avatar entity handle.
@@ -240,10 +248,10 @@ ListenToGameEvent("physgun_pickup", function (params)
end, nil)
---@class PlayerEventItemPickup : GameEventItemPickup
----@field item EntityHandle # The entity handle of the item that was picked up.
----@field item_class string # Classname of the entity that was picked up.
----@field hand CPropVRHand # The entity handle of the hand that picked up the item.
----@field otherhand CPropVRHand # The entity handle of the opposite hand.
+---@field item EntityHandle # The entity handle of the item that was picked up
+---@field item_class string # Classname of the entity that was picked up
+---@field hand CPropVRHand # The entity handle of the hand that picked up the item
+---@field otherhand CPropVRHand # The entity handle of the opposite hand
---Tracking player held items.
---@param data GameEventItemPickup
@@ -316,12 +324,11 @@ end
ListenToGameEvent("item_pickup", listenEventItemPickup, nil)
---@class PlayerEventItemReleased : GameEventItemReleased
----@field item EntityHandle # The entity handle of the item that was dropped.
----@field item_class string # Classname of the entity that was dropped.
----@field hand CPropVRHand # The entity handle of the hand that dropped the item.
----@field otherhand CPropVRHand # The entity handle of the opposite hand.
+---@field item EntityHandle # The entity handle of the item that was dropped
+---@field item_class string # Classname of the entity that was dropped
+---@field hand CPropVRHand # The entity handle of the hand that dropped the item
+---@field otherhand CPropVRHand # The entity handle of the opposite hand
--- ---@type "item_hlvr_crafting_currency_small"|"item_hlvr_crafting_currency_large"|nil
---@type EntityHandle|nil
local last_resin_dropped
@@ -385,9 +392,10 @@ end
ListenToGameEvent("primary_hand_changed", listenEventPrimaryHandChanged, nil)
-- Inherit from base instead of event to remove 'ammoType'
+
---@class PlayerEventPlayerDropAmmoInBackpack : GameEventBase
----@field ammotype "Pistol"|"SMG1"|"Buckshot"|"AlyxGun" # Type of ammo that was stored.
----@field ammo_amount 0|1|2|3|4 # Amount of ammo stored for the given type (1 clip, 2 shells).
+---@field ammotype "Pistol"|"SMG1"|"Buckshot"|"AlyxGun" # Type of ammo that was stored
+---@field ammo_amount integer # Amount of ammo stored for the given type, e.g. 1 clip, 2 shells
---Ammo tracking
---@param data GameEventPlayerDropAmmoInBackpack
@@ -463,8 +471,8 @@ ListenToGameEvent("player_drop_ammo_in_backpack", listenEventPlayerDropAmmoInBac
-- Inherit from base instead of event to remove 'ammoType'
---@class PlayerEventPlayerRetrievedBackpackClip : GameEventBase
----@field ammotype "Pistol"|"SMG1"|"Buckshot"|"AlyxGun" # Type of ammo that was retrieved.
----@field ammo_amount integer # Amount of ammo retrieved for the given type (1 clip, 2 shells).
+---@field ammotype "Pistol"|"SMG1"|"Buckshot"|"AlyxGun" # Type of ammo that was retrieved
+---@field ammo_amount integer # Amount of ammo retrieved for the given type, e.g. 1 clip, 2 shells
---Ammo tracking
---@param data GameEventPlayerRetrievedBackpackClip
@@ -521,9 +529,9 @@ end
ListenToGameEvent("player_retrieved_backpack_clip", listenEventPlayerRetrievedBackpackClip, nil)
---@class PlayerEventPlayerStoredItemInItemholder : GameEventPlayerStoredItemInItemholder
----@field item EntityHandle # The entity handle of the item that stored.
----@field item_class string # Classname of the entity that was stored.
----@field hand CPropVRHand # Hand that the entity was stored in.
+---@field item EntityHandle # The entity handle of the item that stored
+---@field item_class string # Classname of the entity that was stored
+---@field hand CPropVRHand # Hand that the entity was stored in
---Wrist tracking
---@param data GameEventPlayerStoredItemInItemholder
@@ -563,9 +571,9 @@ end
ListenToGameEvent("player_stored_item_in_itemholder", listenEventPlayerStoredItemInItemholder, nil)
---@class PlayerEventPlayerRemovedItemFromItemholder : GameEventPlayerRemovedItemFromItemholder
----@field item EntityHandle # The entity handle of the item that removed.
----@field item_class string # Classname of the entity that was removed.
----@field hand CPropVRHand # Hand that the entity was removed form.
+---@field item EntityHandle # The entity handle of the item that removed
+---@field item_class string # Classname of the entity that was removed
+---@field hand CPropVRHand # Hand that the entity was removed form
---Tracking wrist items
---@param data GameEventPlayerRemovedItemFromItemholder
@@ -612,7 +620,7 @@ ListenToGameEvent("player_removed_item_from_itemholder", listenEventPlayerRemove
-- No known way to track resin being taken out reliably.
---@class PlayerEventPlayerDropResinInBackpack : GameEventPlayerDropResinInBackpack
----@field resin_ent EntityHandle? # The resin entity being dropped into the backpack.
+---@field resin_ent EntityHandle? # The resin entity being dropped into the backpack
---Track resin
---@param data GameEventPlayerDropResinInBackpack
@@ -641,9 +649,9 @@ end
ListenToGameEvent("player_drop_resin_in_backpack", listenEventPlayerDropResinInBackpack, nil)
---@class PlayerEventWeaponSwitch : GameEventWeaponSwitch
----@field item EntityHandle|nil # The handle of the weapon being switched to or nil if no weapon.
----@field item_class string # Classname of the entity that was switched to.
----@field hand CPropVRHand # Hand that the entity was switched to.
+---@field item EntityHandle|nil # The handle of the weapon being switched to or nil if no weapon
+---@field item_class string # Classname of the entity that was switched to
+---@field hand CPropVRHand # Hand that the entity was switched to
---Track weapon equipped
---@param data GameEventWeaponSwitch
diff --git a/scripts/vscripts/alyxlib/player/hands.lua b/scripts/vscripts/alyxlib/player/hands.lua
index d4f57cf..586db69 100644
--- a/scripts/vscripts/alyxlib/player/hands.lua
+++ b/scripts/vscripts/alyxlib/player/hands.lua
@@ -4,13 +4,18 @@
Code for player hands.
+ If not using `alyxlib/init.lua`, load this file at game start using the following line:
+
+ require "alyxlib.player.hands"
]]
local version = "v1.3.0"
----Merge an existing prop with this hand.
----@param prop EntityHandle|string # The prop handle or targetname.
----@param hide_hand boolean # If the hand should turn invisible after merging.
+---
+---Merges an existing prop with this hand.
+---
+---@param prop EntityHandle|string # The prop handle or targetname
+---@param hide_hand boolean # `true` if the hand should turn invisible after merging
function CPropVRHand:MergeProp(prop, hide_hand)
if type(prop) == "string" then
prop = Entities:FindByName(nil, prop)
@@ -29,14 +34,18 @@ function CPropVRHand:MergeProp(prop, hide_hand)
end
end
----Return true if this hand is currently holding a prop.
----@return boolean
+---
+---Checks if this hand is currently holding a prop.
+---
+---@return boolean # `true` if the hand is holding a prop
function CPropVRHand:IsHoldingItem()
return IsEntity(self.ItemHeld, true)
end
----Drop the item held by this hand.
----@return EntityHandle?
+---
+---Drops the item held by this hand.
+---
+---@return EntityHandle? # The entity that was dropped
function CPropVRHand:Drop()
if self.ItemHeld ~= nil then
Player:DropByHandle(self.ItemHeld)
@@ -45,8 +54,10 @@ function CPropVRHand:Drop()
end
end
----Grab the entity
----@param ent EntityHandle|string
+---
+---Forces this hand to grab an entity.
+---
+---@param ent EntityHandle|string # The entity or targetname to grab
function CPropVRHand:Grab(ent)
if type(ent) == "string" then
local name = ent
@@ -59,34 +70,44 @@ function CPropVRHand:Grab(ent)
ent:Grab(self)
end
----Get the rendered glove entity for this hand, i.e. the first `hlvr_prop_renderable_glove` class.
----@return EntityHandle|nil
+---
+---Gets the rendered glove entity for this hand,
+---i.e. the first `hlvr_prop_renderable_glove` class.
+---
+---@return EntityHandle? # The glove entity
function CPropVRHand:GetGlove()
return self:GetFirstChildWithClassname("hlvr_prop_renderable_glove")
end
----Get the entity for this hands grabbity glove (the animated part on the glove).
----@return EntityHandle|nil
+---
+---Gets grabbity glove entity for this hand (the animated part on the glove).
+---
+---@return EntityHandle|nil # The grabbity glove
function CPropVRHand:GetGrabbityGlove()
return self:GetFirstChildWithClassname("prop_grabbity_gloves")
end
----Returns true if the digital action is on for this. See `ENUM_DIGITAL_INPUT_ACTIONS` for action index values.
+---
+---Checks if a digital action is on for this hand.
+---
---Note: Only reports input when headset is awake. Will still transmit input when controller loses tracking.
----@param digitalAction DigitalInputAction
----@return boolean
+---
+---@see DigitalInputAction
+---@param digitalAction DigitalInputAction # The action to check
+---@return boolean # `true` if the action is on
function CPropVRHand:IsButtonPressed(digitalAction)
return Player:IsDigitalActionOnForHand(self.Literal, digitalAction)
end
---
----Get the position of the palm of this hand.
+---Gets the position of the palm of this hand.
---
---Returns the palm of the glove if it exists, otherwise the palm of the invisible hand.
+---
---Sometimes the glove becomes desynchronized with the hand, such as interacting with a handpose or holding a weapon,
---so this function will try to return the position of the visible palm whenever possible.
---
----@return Vector
+---@return Vector # The palm position
function CPropVRHand:GetPalmPosition()
local glove = self:GetGlove()
if glove then
@@ -99,7 +120,7 @@ end
---
---Gets the 'hand_use_controller' entity associated with this hand.
---
----@return EntityHandle
+---@return EntityHandle # The hand_use_controller
function CPropVRHand:GetHandUseController()
for _, controller in ipairs(Entities:FindAllByClassname("hand_use_controller")) do
if controller:GetOwner() == self then
@@ -109,16 +130,20 @@ function CPropVRHand:GetHandUseController()
end
end
+---
---Forces the player to drop this entity if held.
----@param self CBaseEntity
+---
function CBaseEntity:Drop()
Player:DropByHandle(self)
end
Expose(CBaseEntity.Drop, "Drop", CBaseEntity)
----Force the player to grab this entity with a hand.
+---
+---Forces the player to grab this entity with a hand.
+---
---If no hand is supplied then the nearest hand will be used.
----@param hand? CPropVRHand|0|1
+---
+---@param hand? CPropVRHand|0|1 # Hand to grab with
function CBaseEntity:Grab(hand)
Player:GrabByHandle(self, hand)
end
diff --git a/scripts/vscripts/alyxlib/player/wrist_attachments.lua b/scripts/vscripts/alyxlib/player/wrist_attachments.lua
index b7b84e6..e9de56a 100644
--- a/scripts/vscripts/alyxlib/player/wrist_attachments.lua
+++ b/scripts/vscripts/alyxlib/player/wrist_attachments.lua
@@ -1,6 +1,13 @@
--[[
v1.2.0
https://github.com/FrostSource/alyxlib
+
+ Wrist attachments allow you to attach entities to the player's hands
+ in an addon shared way.
+
+ If not using `alyxlib/init.lua`, load this file at game start using the following line:
+
+ require "alyxlib.player.wrist_attachments"
]]
---@class WristAttachments
@@ -53,14 +60,15 @@ local function getHand(attachment)
end
end
-
----Add a new entity as a wrist attachment.
----@param entity EntityHandle # The entity which will become a wrist attachment.
----@param hand? WristAttachmentHandType # The hand type to attach to initially.
----@param length number # Physical length of the entity to make sure it will not overlap with other wrist attachments.
----@param priority number? # Priority for the entity when there are other wrist attacments. Lower number is higher priority. Cannot specify a value lower than 0.
----@param offset Vector? # Optional offset for the entity (x component is ignored).
----@param angles QAngle? # Optional angles for the entity.
+---
+---Adds a new entity as a wrist attachment.
+---
+---@param entity EntityHandle # The entity which will become a wrist attachment
+---@param hand? WristAttachmentHandType # The hand type to attach to initially
+---@param length number # Physical length of the entity to make sure it will not overlap with other wrist attachments
+---@param priority number? # Priority for the entity when there are other wrist attacments - lower number is higher priority, cannot specify a value lower than 0
+---@param offset Vector? # Optional origin offset for the entity (x component is ignored)
+---@param angles QAngle? # Optional angles offset for the entity
function WristAttachments:Add(entity, hand, length, priority, offset, angles)
if self:IsEntityAttached(entity) then
@@ -81,11 +89,13 @@ function WristAttachments:Add(entity, hand, length, priority, offset, angles)
self:Update()
end
----Set the hand that the entity should be attached to.
----@param entity EntityHandle # The entity to change data for.
----@param hand WristAttachmentHandType # The type of hand to attach to.
----@param offset Vector # Optional offset for the entity (x component is ignored).
----@param angles QAngle # Optional angles for the entity.
+---
+---Sets the hand that the entity should be attached to.
+---
+---@param entity EntityHandle # The entity to change data for
+---@param hand WristAttachmentHandType # The type of hand to attach to
+---@param offset? Vector # Optional origin offset for the entity (x component is ignored)
+---@param angles? QAngle # Optional angles offset for the entity
function WristAttachments:SetHand(entity, hand, offset, angles)
local attachment = self:GetEntityAttachment(entity)
if attachment then
@@ -98,9 +108,11 @@ function WristAttachments:SetHand(entity, hand, offset, angles)
end
end
----Get the hand that the entity is attached to.
----@param entity EntityHandle
----@return CPropVRHand?
+---
+---Gets the hand that the entity is attached to.
+---
+---@param entity EntityHandle # The entity to get the hand for
+---@return CPropVRHand? # The hand that the entity is attached to
function WristAttachments:GetHand(entity)
for _, attachment in ipairs(self.attachments) do
if attachment.entity == entity then
@@ -109,9 +121,11 @@ function WristAttachments:GetHand(entity)
end
end
----Get the attachment data related to an attach entity.
----@param entity EntityHandle # The entity to get the data for.
----@return WristAttachmentData? # The attachment data for the entity, if it is attached.
+---
+---Gets the attachment data related to an attach entity.
+---
+---@param entity EntityHandle # The entity to get the data for
+---@return WristAttachmentData? # The attachment data for the entity, or `nil` if not found
function WristAttachments:GetEntityAttachment(entity)
for _, attachment in ipairs(self.attachments) do
if attachment.entity == entity then
@@ -121,13 +135,21 @@ function WristAttachments:GetEntityAttachment(entity)
return nil
end
----Get if an entity is attached to a wrist using this system.
----@param entity EntityHandle # The entity to check.
----@return boolean # True if attached, false otherwise.
+---
+---Checks if an entity is attached to a wrist using this system.
+---
+---@param entity EntityHandle # The entity to check
+---@return boolean # `true` if attached, `false` otherwise.
function WristAttachments:IsEntityAttached(entity)
return self:GetEntityAttachment(entity) ~= nil
end
+---
+---Updates the attachments.
+---
+---This is called automatically when an attachment is added or removed
+---or when the player's primary hand changes.
+---
function WristAttachments:Update()
local offsets = {
[Player.LeftHand] = 1,
diff --git a/scripts/vscripts/alyxlib/precache.lua b/scripts/vscripts/alyxlib/precache.lua
index b575dde..de9c35d 100644
--- a/scripts/vscripts/alyxlib/precache.lua
+++ b/scripts/vscripts/alyxlib/precache.lua
@@ -5,22 +5,9 @@
Precaching can only be done with an entity attached script, so this script collects a list of assets to be automatically
precached when the player spawns, allowing you to precache assets from your global scripts.
- If not using `vscripts/alyxlib/core.lua`, load this file at game start using the following line:
+ If not using `alyxlib/init.lua`, load this file at game start using the following line:
- ```lua
require "alyxlib.precache"
- ```
-
- ======================================== Usage ========================================
-
- Add assets to be precached in the following way:
-
- ```lua
- GlobalPrecache("model", "models/weapons/vr_alyxgun/vr_alyxgun_clip.vmdl")
- GlobalPrecache("entity", "prop_dynamic", {
- model = "models/weapons/vr_alyxgun/vr_alyxgun_clip.vmdl"
- })
- ```
]]
if thisEntity then
---@param context CScriptPrecacheContext
@@ -44,10 +31,13 @@ require "alyxlib.player.core"
---| "model"
---| "entity"
+---
+---A global asset to be precached when the player spawns.
+---
---@class AlyxLibGlobalPrecacheItem
----@field type AlyxLibGlobalPrecacheType # The type of asset to precache.
----@field path string # The asset path to precache (or the classname if type is entity).
----@field spawnkeys table? # The spawnkeys table if type is entity.
+---@field type AlyxLibGlobalPrecacheType # The type of asset to precache
+---@field path string # The asset path to precache (or the classname if type is entity)
+---@field spawnkeys table? # The spawnkeys table if type is entity
---@type AlyxLibGlobalPrecacheItem[]
_G.AlyxLibGlobalPrecacheList = {}
@@ -57,9 +47,9 @@ _G.AlyxLibGlobalPrecacheList = {}
---
---If you are precaching *after* the player has spawned, then you must also call [GlobalPrecacheFlush](lua://GlobalPrecacheFlush).
---
----@param type AlyxLibGlobalPrecacheType # The type of asset to precache.
----@param path string # The asset path to precache (or the classname if type is entity).
----@param spawnkeys table? # The spawnkeys table if type is entity.
+---@param type AlyxLibGlobalPrecacheType # The type of asset to precache
+---@param path string # The asset path to precache (or the classname if type is entity)
+---@param spawnkeys table? # The spawnkeys table if type is entity
function GlobalPrecache(type, path, spawnkeys)
table.insert(AlyxLibGlobalPrecacheList, {
type = type,
@@ -69,7 +59,7 @@ function GlobalPrecache(type, path, spawnkeys)
end
---Starts the precache process.
----@param callback? function # The function to call when the precaching is complete.
+---@param callback? function # The function to call when the precaching is complete
local function precacheAsync(callback)
SpawnEntityFromTableAsynchronous("logic_script", {
vscripts = "alyxlib/precache",
@@ -88,7 +78,7 @@ end
---
---If you are precaching *after* the player has spawned, call this function after preceding [GlobalPrecache](lua://GlobalPrecache) calls.
---
----@param callback? function # The function to call when the precaching is complete.
+---@param callback? function # The function to call when the precaching is complete
function GlobalPrecacheFlush(callback)
precacheAsync(function()
AlyxLibGlobalPrecacheList = {}
@@ -102,6 +92,7 @@ end
---Internal function used to start the precache process.
---
---**This should only be called manually if you know what you're doing!**
+---
---@param context CScriptPrecacheContext
function _PrecacheGlobalItems(context)
if #AlyxLibGlobalPrecacheList > 0 then
diff --git a/scripts/vscripts/alyxlib/storage.lua b/scripts/vscripts/alyxlib/storage.lua
index 78f7674..faa8561 100644
--- a/scripts/vscripts/alyxlib/storage.lua
+++ b/scripts/vscripts/alyxlib/storage.lua
@@ -6,122 +6,9 @@
Data is saved on a specific entity and if the entity is killed the values
cannot be retrieved during that game session.
- If not using `vscripts/alyxlib/core.lua`, load this file at game start using the following line:
+ If not using `alyxlib/init.lua`, load this file at game start using the following line:
- ```lua
require "alyxlib.storage"
- ```
-
- ======================================== Usage ==========================================
-
-
-
- Functions are accessed through the global 'Storage' table.
- When using dot notation (Storage.SaveNumber) you must provide the entity handle to save on.
- When using colon notation (Storage:SaveNumber) the script will find the best handle to save on (usually the player).
-
- Examples of storing and retrieving the following local values that might be defined
- at the top of the file:
-
- ```lua
- -- Getting the values to save
- local origin = thisEntity:GetOrigin()
- local hp = thisEntity:GetHealth()
- local name = thisEntity:GetName()
-
- -- Saving the values:
-
- Storage:SaveVector("origin", origin)
- Storage:SaveNumber("hp", hp)
- Storage:SaveString("name", name)
-
- -- Loading the values:
-
- origin = Storage:LoadVector("origin")
- hp = Storage:LoadNumber("hp")
- name = Storage:LoadString("name")
- ```
-
- This script also provides functions Storage.Save/Storage.Load for general purpose
- type inference storage. It is still recommended that you use the explicit type
- functions to keep code easily understandable, but it can help with prototyping:
-
- ```lua
- Storage:Save("origin", origin)
- Storage:Save("hp", hp)
- Storage:Save("name", name)
-
- origin = Storage:Load("origin", origin)
- hp = Storage:Load("hp", hp)
- name = Storage:Load("name", name)
- ```
-
- Entity versions of the functions exist to make saving to a specific entity easier:
-
- ```lua
- thisEntity:SaveNumber("hp", thisEntity:GetHealth())
- thisEntity:SetHealth(thisEntity:LoadNumber("hp"))
- ```
-
- ======================================= Complex Tables ========================================
-
- Since Lua allows tables to have keys and values to be virtually any value, `Storage.SaveTable`
- attempts to save and restore both using their native Storage functions, and does so recursively
- meaning nested tables can be saved.
-
- An example of safely restoring a class instance (assuming the class has a `new` method)
- where an instance is created and then the saved values are restored and added to the instance:
-
- ```lua
- vlua.tableadd(MyClass:new(), thisEntity:LoadTable("MyClass", {}))
- ```
-
- Alternatively if the class uses a metatable and you have access to it:
-
- ```lua
- setmetatable(thisEntity:LoadTable("MyClass", {}), MyClass)
- ```
-
- Functions for both key and value are currently not supported and will fail to save but will not
- block the rest of the table from being saved. This means you can save whole class tables and
- restore them with only the relevant saved data.
-
- =================================== Delegate Save Functions ===================================
-
- Tables can utilize __save/__load functions to handle their specific saving needs.
- The table with these functions needs to be registered with the Storage module and any instances
- must have an `__index` key pointing to the base table:
-
- ```lua
- MyClass.__index = MyClass
- Storage.RegisterType("MyClass", MyClass)
- ```
-
- The functions may handle the data saving however they want but should generally use the base
- Storage functions to keep things safe and consistent. The below template functions may be
- copied to your script as a starting point.
- See `data/stack.lua` for an example of these functions in action.
-
- ```lua
- ---@param handle EntityHandle # The entity to save on.
- ---@param name string # The name to save as.
- ---@param object MyClass # The object to save.
- ---@return boolean # If the save was successful.
- function MyClass.__save(handle, name, object)
- return Storage.SaveTableCustom(handle, name, object, "MyClass")
- end
-
- ---@param handle EntityHandle # Entity to load from.
- ---@param name string # Name to load.
- ---@return MyClass|nil # The loaded value. nil on fail.
- function MyClass.__load(handle, name)
- local object = Storage.LoadTableCustom(handle, name, "MyClass")
- if object == nil then return nil end
- return setmetatable(object, MyClass)
- end
- ```
-
- Storage.Save and Storage.Load will now invoke these functions when appropriate.
=========================================== Notes =============================================
@@ -146,10 +33,10 @@ local function Warn(msg)
end
---
----Resolve a given handle.
+---Resolves a given handle.
---
----@param handle EntityHandle # Handle to resolve.
----@return EntityHandle # Resolved save handle.
+---@param handle EntityHandle # Handle to resolve
+---@return EntityHandle # Resolved save handle
local function resolveHandle(handle)
-- Keep given handle if valid entity
if handle ~= nil and handle ~= Storage and IsValidEntity(handle) then
@@ -179,41 +66,42 @@ Storage.type_to_class = {}
Storage.class_to_type = {}
---
----Register a class table type with a name.
+---Registers a class table type with a name.
---
----@param name string # Name that the type will be saved as.
----@param T table # Class table.
+---@param name string # Name that the type will be saved as
+---@param T table # Class table
function Storage.RegisterType(name, T)
Storage.type_to_class[name] = T
Storage.class_to_type[T] = name
end
---
----Unregister a class type.
+---Unregisters a class type.
---
----@param name string # Name to unregister.
----@param T table # Class to unregister.
+---@param name string # Name to unregister
+---@param T table # Class to unregister
function Storage.UnregisterType(name, T)
Storage.type_to_class[name] = nil
Storage.class_to_type[T] = nil
end
---
----Join a list of values by the hidden separator.
+---Joins a list of values by the hidden separator.
---
----@param ... any # Values to join.
----@return string # Joined string.
+---@param ... any # Values to join
+---@return string # Joined string
function Storage.Join(...)
return table.concat({...}, separator)
end
---
---Helper function for saving the type correctly.
+---
---No failsafes are provided in this function, you must be sure you are saving correctly.
---
----@param handle EntityHandle # Entity to save on.
----@param name string # Name prefix to save as.
----@param T string # String name of `T`.
+---@param handle EntityHandle # Entity to save on
+---@param name string # Name prefix to save as
+---@param T string # String name of `T`
function Storage.SaveType(handle, name, T)
handle:SetContext(Storage.Join(name, "type"), T, 0)
end
@@ -223,12 +111,12 @@ end
------------
---
----Save a string.
+---Saves a string.
---
----@param handle EntityHandle # Entity to save on.
----@param name string # Name to save as.
----@param value string|nil # String to save.
----@return boolean # If the save was successful.
+---@param handle EntityHandle # Entity to save on
+---@param name string # Name to save as
+---@param value string|nil # String to save
+---@return boolean # If the save was successful
function Storage.SaveString(handle, name, value)
handle = resolveHandle(handle)
if not handle then
@@ -260,12 +148,12 @@ function Storage.SaveString(handle, name, value)
end
---
----Save a number.
+---Saves a number.
---
----@param handle EntityHandle # Entity to save on.
----@param name string # Name to save as.
----@param value number # Number to save.
----@return boolean # If the save was successful.
+---@param handle EntityHandle # Entity to save on
+---@param name string # Name to save as
+---@param value number # Number to save
+---@return boolean # If the save was successful
function Storage.SaveNumber(handle, name, value)
handle = resolveHandle(handle)
if not handle then
@@ -278,12 +166,12 @@ function Storage.SaveNumber(handle, name, value)
end
---
----Save a boolean.
+---Saves a boolean.
---
----@param handle EntityHandle # Entity to save on.
----@param name string # Name to save as.
----@param bool boolean # Boolean to save.
----@return boolean # If the save was successful.
+---@param handle EntityHandle # Entity to save on
+---@param name string # Name to save as
+---@param bool boolean # Boolean to save
+---@return boolean # If the save was successful
function Storage.SaveBoolean(handle, name, bool)
handle = resolveHandle(handle)
if not handle then
@@ -296,12 +184,12 @@ function Storage.SaveBoolean(handle, name, bool)
end
---
----Save a Vector.
+---Saves a Vector.
---
----@param handle EntityHandle # Entity to save on.
----@param name string # Name to save as.
----@param vector Vector # Vector to save.
----@return boolean # If the save was successful.
+---@param handle EntityHandle # Entity to save on
+---@param name string # Name to save as
+---@param vector Vector # Vector to save
+---@return boolean # If the save was successful
function Storage.SaveVector(handle, name, vector)
handle = resolveHandle(handle)
if not handle then
@@ -317,12 +205,12 @@ function Storage.SaveVector(handle, name, vector)
end
---
----Save a QAngle.
+---Saves a QAngle.
---
----@param handle EntityHandle # Entity to save on.
----@param name string # Name to save as.
----@param qangle QAngle # QAngle to save.
----@return boolean # If the save was successful.
+---@param handle EntityHandle # Entity to save on
+---@param name string # Name to save as
+---@param qangle QAngle # QAngle to save
+---@return boolean # If the save was successful
function Storage.SaveQAngle(handle, name, qangle)
handle = resolveHandle(handle)
if not handle then
@@ -338,17 +226,17 @@ function Storage.SaveQAngle(handle, name, qangle)
end
---
----Save a table with a custom type.
+---Saves a table with a custom type.
---Should be used with custom save functions.
---
---If trying to save a normal table use `Storage.SaveTable`.
---
----@param handle EntityHandle # Entity to save on.
----@param name string # Name to save as.
----@param tbl table # Table to save.
----@param T string # Type to save as.
----@param save_meta? boolean # If keys starting with '__' should be saved.
----@return boolean # If the save was successful.
+---@param handle EntityHandle # Entity to save on
+---@param name string # Name to save as
+---@param tbl table # Table to save
+---@param T string # Type to save as
+---@param save_meta? boolean # If keys starting with '__' should be saved
+---@return boolean # If the save was successful
function Storage.SaveTableCustom(handle, name, tbl, T, save_meta)
handle = resolveHandle(handle)
if not handle then
@@ -378,30 +266,30 @@ function Storage.SaveTableCustom(handle, name, tbl, T, save_meta)
end
---
----Save a table.
+---Saves a table.
---
---May be ordered, unordered or mixed.
---
---May have nested tables.
---
----@param handle EntityHandle # Entity to save on.
----@param name string # Name to save as.
----@param tbl table # Table to save.
----@return boolean # If the save was successful.
+---@param handle EntityHandle # Entity to save on
+---@param name string # Name to save as
+---@param tbl table # Table to save
+---@return boolean # If the save was successful
function Storage.SaveTable(handle, name, tbl)
return Storage.SaveTableCustom(handle, name, tbl, "table")
end
---
----Save an entity reference.
+---Saves an entity reference.
---
---Entity handles change between game sessions so this function
---modifies the passed entity to make sure it can keep track of it.
---
----@param handle EntityHandle # Entity to save on.
----@param name string # Name to save as.
----@param entity EntityHandle|nil # Entity to save.
----@return boolean # If the save was successful.
+---@param handle EntityHandle # Entity to save on
+---@param name string # Name to save as
+---@param entity EntityHandle|nil # Entity to save
+---@return boolean # If the save was successful
function Storage.SaveEntity(handle, name, entity, useClassname)
handle = resolveHandle(handle)
@@ -441,15 +329,15 @@ local _vector = getmetatable(Vector())
local _qangle = getmetatable(QAngle())
---
----Save a value.
+---Saves a value.
---
---Uses type inference to save the value.
---If you are experiencing errors consider saving with one of the explicit type saves.
---
----@param handle EntityHandle # Entity to save on.
----@param name string # Name to save as.
----@param value any # Value to save.
----@return boolean # If the save was successful.
+---@param handle EntityHandle # Entity to save on
+---@param name string # Name to save as
+---@param value any # Value to save
+---@return boolean # If the save was successful
function Storage.Save(handle, name, value)
local t = type(value)
if t=="function" then Warn("Functions are not supported for saving yet.") return false
@@ -478,13 +366,13 @@ end
-------------
---
----Load a string.
+---Loads a string.
---
---@generic T
----@param handle EntityHandle # Entity to load from.
----@param name string # Name the string was saved as.
----@param default? T # Optional default value.
----@return string|T # Saved string or `default`.
+---@param handle EntityHandle # Entity to load from
+---@param name string # Name the string was saved as
+---@param default? T # Optional default value
+---@return string|T # Saved string or `default`
function Storage.LoadString(handle, name, default)
handle = resolveHandle(handle)
local t = handle:GetContext(name..separator.."type")
@@ -505,13 +393,13 @@ function Storage.LoadString(handle, name, default)
end
---
----Load a number.
+---Loads a number.
---
---@generic T
----@param handle EntityHandle # Entity to load from.
----@param name string # Name the number was saved as.
----@param default? T # Optional default value.
----@return number|T # Saved number or `default`.
+---@param handle EntityHandle # Entity to load from
+---@param name string # Name the number was saved as
+---@param default? T # Optional default value
+---@return number|T # Saved number or `default`
function Storage.LoadNumber(handle, name, default)
handle = resolveHandle(handle)
local t = handle:GetContext(name..separator.."type")
@@ -525,13 +413,13 @@ function Storage.LoadNumber(handle, name, default)
end
---
----Load a boolean value.
+---Loads a boolean value.
---
---@generic T
----@param handle EntityHandle # Entity to load from.
----@param name string # Name the boolean was saved as.
----@param default? T # Optional default value.
----@return boolean|T # Saved boolean or `default`.
+---@param handle EntityHandle # Entity to load from
+---@param name string # Name the boolean was saved as
+---@param default? T # Optional default value
+---@return boolean|T # Saved boolean or `default`
function Storage.LoadBoolean(handle, name, default)
handle = resolveHandle(handle)
local t = handle:GetContext(name..separator.."type")
@@ -544,13 +432,13 @@ function Storage.LoadBoolean(handle, name, default)
end
---
----Load a Vector.
+---Loads a Vector.
---
---@generic T
----@param handle EntityHandle # Entity to load from.
----@param name string # Name the Vector was saved as.
----@param default? T # Optional default value.
----@return Vector|T # Saved Vector or `default`.
+---@param handle EntityHandle # Entity to load from
+---@param name string # Name the Vector was saved as
+---@param default? T # Optional default value
+---@return Vector|T # Saved Vector or `default`
function Storage.LoadVector(handle, name, default)
handle = resolveHandle(handle)
local t = handle:GetContext(name..separator.."type")
@@ -566,13 +454,13 @@ function Storage.LoadVector(handle, name, default)
end
---
----Load a QAngle.
+---Loads a QAngle.
---
---@generic T
----@param handle EntityHandle # Entity to load from.
----@param name string # Name the QAngle was saved as.
----@param default? T # Optional default value.
----@return QAngle|T # Saved QAngle or `default`.
+---@param handle EntityHandle # Entity to load from
+---@param name string # Name the QAngle was saved as
+---@param default? T # Optional default value
+---@return QAngle|T # Saved QAngle or `default`
function Storage.LoadQAngle(handle, name, default)
handle = resolveHandle(handle)
local t = handle:GetContext(name..separator.."type")
@@ -588,17 +476,17 @@ function Storage.LoadQAngle(handle, name, default)
end
---
----Load a table with a custom type.
+---Loads a table with a custom type.
---Should be used with custom load functions.
---
---If trying to load a normal table use `Storage.LoadTable`.
---
---@generic T
----@param handle EntityHandle # Entity to load from.
----@param name string # Name the table was saved as.
----@param T string # Type to save as.
----@param default? T # Optional default value.
----@return table|T # Saved table or `default`.
+---@param handle EntityHandle # Entity to load from
+---@param name string # Name the table was saved as
+---@param T string # Type to save as
+---@param default? T # Optional default value
+---@return table|T # Saved table or `default`
function Storage.LoadTableCustom(handle, name, T, default)
handle = resolveHandle(handle)
local name_sep = name..separator
@@ -621,25 +509,25 @@ function Storage.LoadTableCustom(handle, name, T, default)
end
---
----Load a table with a custom type.
+---Loads a table with a custom type.
---
---@generic T
----@param handle EntityHandle # Entity to load from.
----@param name string # Name the table was saved as.
----@param default? T # Optional default value.
----@return table|T # Saved table or `default`.
+---@param handle EntityHandle # Entity to load from
+---@param name string # Name the table was saved as
+---@param default? T # Optional default value
+---@return table|T # Saved table or `default`
function Storage.LoadTable(handle, name, default)
return Storage.LoadTableCustom(handle, name, "table", default)
end
---
----Load an entity.
+---Loads an entity.
---
---@generic T
----@param handle EntityHandle # Entity to load from.
----@param name string # Name to save as.
----@param default? T # Optional default value.
----@return EntityHandle|T # Saved entity or `default`.
+---@param handle EntityHandle # Entity to load from
+---@param name string # Name to save as
+---@param default? T # Optional default value
+---@return EntityHandle|T # Saved entity or `default`
function Storage.LoadEntity(handle, name, default)
handle = resolveHandle(handle)
local t = handle:GetContext(name..separator.."type")
@@ -677,12 +565,12 @@ function Storage.LoadEntity(handle, name, default)
end
---
----Load a value.
+---Loads a value.
---
----@param handle EntityHandle # Entity to load from.
----@param name string # Name the value was saved as.
----@param default? any # Optional default value.
----@return any # Saved value or `default`.
+---@param handle EntityHandle # Entity to load from
+---@param name string # Name the value was saved as
+---@param default? any # Optional default value
+---@return any # Saved value or `default`
function Storage.Load(handle, name, default)
handle = resolveHandle(handle)
local t = handle:GetContext(name..separator.."type")
@@ -709,10 +597,10 @@ function Storage.Load(handle, name, default)
end
end
----Load all values saved to an entity.
----@param handle EntityHandle # Entity to load from.
----@param direct? boolean # Optionally load values directly into `handle` instead of a new table.
----@return table # Table of loaded values (or `handle` if `direct` is true).
+---Loads all values saved to an entity.
+---@param handle EntityHandle # Entity to load from
+---@param direct? boolean # Optionally load values directly into `handle` instead of a new table
+---@return table # Table of loaded values (or `handle` if `direct` is true)
function Storage.LoadAll(handle, direct)
local tbl = direct and handle or {}
handle = resolveHandle(handle)
diff --git a/scripts/vscripts/alyxlib/utils/common.lua b/scripts/vscripts/alyxlib/utils/common.lua
index 88e9d49..a6c0476 100644
--- a/scripts/vscripts/alyxlib/utils/common.lua
+++ b/scripts/vscripts/alyxlib/utils/common.lua
@@ -2,13 +2,12 @@
v3.1.0
https://github.com/FrostSource/alyxlib
- This file contains utility functions to help reduce repetitive code and add general miscellaneous functionality.
+ This file contains utility functions to help reduce repetitive code
+ and add general miscellaneous functionality.
- If not using `vscripts/alyxlib/core.lua`, load this file at game start using the following line:
+ If not using `alyxlib/init.lua`, load this file at game start using the following line:
- ```lua
require "alyxlib.utils.common"
- ```
]]
---
@@ -19,10 +18,10 @@ Util = {}
Util.version = "v3.1.0"
---
----Convert vr_tip_attachment from a game event [1,2] into a hand id [0,1] taking into account left handedness.
+---Converts `vr_tip_attachment` from a game event (1,2) into a hand id (0,1) taking primary hand into account.
---
----@param vr_tip_attachment 1|2
----@return 0|1
+---@param vr_tip_attachment 1|2 # The `vr_tip_attachment` value from a game event
+---@return 0|1 # The hand id
function Util.GetHandIdFromTip(vr_tip_attachment)
local handId = vr_tip_attachment - 1
if not Convars:GetBool("hlvr_left_hand_primary") then
@@ -32,12 +31,13 @@ function Util.GetHandIdFromTip(vr_tip_attachment)
end
---
----Attempt to find a key in `tbl` pointing to `value`.
+---Attempts to find a key in `tbl` pointing to `value`.
---
----@param tbl table # The table to search.
----@param value any # The value to search for.
----@return unknown|nil # The key in `tbl` or nil if no `value` was found.
----@deprecated # Functionally the same as `vlua.find`.
+---@param tbl table # The table to search
+---@param value any # The value to search for
+---@return unknown|nil # The key in `tbl`, or `nil` if no `value` was found
+---@deprecated # Functionally equivalent to [vlua.find](lua://vlua.find)
+---@see vlua.find # functionally equivalent
function Util.FindKeyFromValue(tbl, value)
for key, val in pairs(tbl) do
if val == value then
@@ -48,12 +48,12 @@ function Util.FindKeyFromValue(tbl, value)
end
---
----Attempt to find a key in `tbl` pointing to `value` by recursively searching nested tables.
+---Attempts to find a key in `tbl` pointing to `value` by recursively searching nested tables.
---
----@param tbl table # The table to search.
----@param value any # The value to search for.
----@param seen? table[] # List of tables that have already been searched.
----@return unknown|nil # The key in `tbl` or nil if no `value` was found.
+---@param tbl table # The table to search
+---@param value any # The value to search for
+---@param seen? table[] # List of tables that have already been searched
+---@return unknown|nil # The key in `tbl` or nil if no `value` was found
local function _FindKeyFromValueDeep(tbl, value, seen)
seen = seen or {}
for key, val in pairs(tbl) do
@@ -69,18 +69,22 @@ local function _FindKeyFromValueDeep(tbl, value, seen)
end
---
----Attempt to find a key in `tbl` pointing to `value` by recursively searching nested tables.
+---Attempts to find a key in `tbl` pointing to `value` by recursively searching nested tables.
---
----@param tbl table # The table to search.
----@param value any # The value to search for.
----@return unknown|nil # The key in `tbl` or nil if no `value` was found.
+---@param tbl table # The table to search
+---@param value any # The value to search for
+---@return unknown|nil # The key in `tbl`, or `nil` if no `value` was found.
function Util.FindKeyFromValueDeep(tbl, value)
return _FindKeyFromValueDeep(tbl, value)
end
+---
---Returns the size of any table.
----@param tbl table
----@return integer
+---
+---@param tbl table # The table to get the size of
+---@return integer # The size of the table
+---@deprecated # Functionally equivalent to [TableSize](lua://TableSize)
+---@see TableSize # functionally equivalent
function Util.TableSize(tbl)
local count = 0
for _, _ in pairs(tbl) do
@@ -90,30 +94,34 @@ function Util.TableSize(tbl)
end
---
----Delay some code.
+---Delays some code.
---
----@param func function
----@param delay? number
+---@param func function # The function to delay
+---@param delay? number # The delay in seconds (default: 0)
+---@see CBaseEntity.Delay
function Util.Delay(func, delay)
GetListenServerHost():SetContextThink(DoUniqueString("delay"), func, delay or 0)
end
---
----Get a new `QAngle` from a `Vector`.
+---Gets a new `QAngle` from a `Vector`.
+---
---This simply transfers the raw values from one to the other.
---
----@param vec Vector
----@return QAngle
+---@param vec Vector # The vector
+---@return QAngle # The new QAngle
function Util.QAngleFromVector(vec)
return QAngle(vec.x, vec.y, vec.z)
end
----Create a constraint between two entity handles.
----@param entity1 EntityHandle # First entity to attach.
----@param entity2 EntityHandle|nil # Second entity to attach. Set nil to attach to world.
----@param class? string # Class of constraint, default is `phys_constraint`.
----@param properties? table # Key/value property table.
----@return EntityHandle
+---
+---Creates a constraint between two entity handles.
+---
+---@param entity1 EntityHandle # First entity to attach
+---@param entity2 EntityHandle|nil # Second entity to attach, or `nil` to attach to world
+---@param class? string # Class of constraint (default: `phys_constraint`)
+---@param properties? table # Key/value property table for the constraint
+---@return EntityHandle # The created constraint
function Util.CreateConstraint(entity1, entity2, class, properties)
-- Cache original names
local name1 = entity1:GetName()
@@ -156,13 +164,15 @@ end
---|"none" # "None"
---|"custom" # "Custom"
----Create a damaging explosion effect at a position.
----@param origin Vector
----@param explosionType? ExplosionType
----@param magnitude? number
----@param radiusOverride? number
----@param ignoredEntity? EntityHandle|string # If the entity passed does not have a unique name, all entities with that name will be ignored.
----@param ignoredClass? string
+---
+---Creates a damaging explosion effect at a position.
+---
+---@param origin Vector # Worldspace position
+---@param explosionType? ExplosionType # Explosion type (default: "")
+---@param magnitude? number # Explosion magnitude (default: 100)
+---@param radiusOverride? number # Radius override (default: 0)
+---@param ignoredEntity? EntityHandle|string # Targetname to ignore or entity handle with a name
+---@param ignoredClass? string # Classname to ignore (default: "")
function Util.CreateExplosion(origin, explosionType, magnitude, radiusOverride, ignoredEntity, ignoredClass)
local name
@@ -185,10 +195,12 @@ function Util.CreateExplosion(origin, explosionType, magnitude, radiusOverride,
expl:EntFire("Kill", nil, 0.1)
end
----Choose and return a random argument.
+---
+---Chooses a random value from the provided arguments.
+---
---@generic T
----@param ... T
----@return T
+---@param ... T # Values
+---@return T # Chosen value
function Util.Choose(...)
local args = {...}
local numArgs = #args
@@ -201,9 +213,13 @@ function Util.Choose(...)
end
end
+---
---Turns a string of up to three numbers into a vector.
----@param str string # Should have a format of "x y z"
----@return Vector
+---
+---Should have a format of "x y z"
+---
+---@param str string # String to parse
+---@return Vector # Parsed vector
function Util.VectorFromString(str)
if type(str) ~= "string" then
return Vector()
diff --git a/scripts/vscripts/alyxlib/utils/enums.lua b/scripts/vscripts/alyxlib/utils/enums.lua
index 0488fa4..35ef286 100644
--- a/scripts/vscripts/alyxlib/utils/enums.lua
+++ b/scripts/vscripts/alyxlib/utils/enums.lua
@@ -3,6 +3,10 @@
https://github.com/FrostSource/alyxlib
Missing enumerations.
+
+ If not using `alyxlib/init.lua`, load this file at game start using the following line:
+
+ require "alyxlib.utils.enums"
]]
local version = "v1.0.1"
diff --git a/scripts/vscripts/alyxlib/utils/extension_duplicate_fix.lua b/scripts/vscripts/alyxlib/utils/extension_duplicate_fix.lua
index c368691..9d342b0 100644
--- a/scripts/vscripts/alyxlib/utils/extension_duplicate_fix.lua
+++ b/scripts/vscripts/alyxlib/utils/extension_duplicate_fix.lua
@@ -9,7 +9,7 @@
Duplicates are detected by checking for increments in the "ex*d_" fix-up name pattern the extensions give.
- **This file is not loaded by default, load it with the following line:
+ **This file is not loaded by default, load it with the following line:**
require("alyxlib.utils.extension_duplicate_fix")
]]
From a4a46151006eee8aa8832d6bb95fb0aad3fdef4b Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Tue, 4 Nov 2025 22:43:24 +1300
Subject: [PATCH 103/146] rename parameter
---
scripts/vscripts/alyxlib/globals.lua | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/scripts/vscripts/alyxlib/globals.lua b/scripts/vscripts/alyxlib/globals.lua
index 8bbbaf5..b213f9d 100644
--- a/scripts/vscripts/alyxlib/globals.lua
+++ b/scripts/vscripts/alyxlib/globals.lua
@@ -605,13 +605,13 @@ function TablePluck(tbl, key)
end
---
----Returns the index of the first value that matches the predicate.
+---Returns the key of the first value that matches the predicate.
---
----@param list table # List to search
+---@param tbl table # Table to search
---@param predicate fun(value:any):boolean # Predicate function
----@return any # Index of the first value that matches the predicate
-function TableFindKey(list, predicate)
- for i, v in pairs(list) do
+---@return any # Key of the first value that matches the predicate
+function TableFindKey(tbl, predicate)
+ for i, v in pairs(tbl) do
if predicate(v) then
return i
end
From 53e6a2d2572b834b595c80bad739a52dec7b3ad0 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Tue, 4 Nov 2025 22:50:13 +1300
Subject: [PATCH 104/146] fix possible nil reference and table holes
---
scripts/vscripts/alyxlib/player/core.lua | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/scripts/vscripts/alyxlib/player/core.lua b/scripts/vscripts/alyxlib/player/core.lua
index 009e648..3aca262 100644
--- a/scripts/vscripts/alyxlib/player/core.lua
+++ b/scripts/vscripts/alyxlib/player/core.lua
@@ -498,12 +498,13 @@ end
---
---@return EntityHandle[] # List of items
function CBasePlayer:GetImmediateItems()
- return {
- self.LeftHand.WristItem,
- self.RightHand.WristItem,
- self.LeftHand.ItemHeld,
- self.RightHand.ItemHeld
- }
+ local lh, rh = self.LeftHand, self.RightHand
+ local items = {}
+ if lh and lh.WristItem then items[#items+1] = lh.WristItem end
+ if rh and rh.WristItem then items[#items+1] = rh.WristItem end
+ if lh and lh.ItemHeld then items[#items+1] = lh.ItemHeld end
+ if rh and rh.ItemHeld then items[#items+1] = rh.ItemHeld end
+ return items
end
---
From 8bf5084bb2f1381fad707b80a068605e3e4bffd4 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Tue, 4 Nov 2025 22:53:50 +1300
Subject: [PATCH 105/146] update annotations
---
scripts/vscripts/alyxlib/controls/input.lua | 2 +-
scripts/vscripts/alyxlib/player/events.lua | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/scripts/vscripts/alyxlib/controls/input.lua b/scripts/vscripts/alyxlib/controls/input.lua
index b0f0d6a..100bc67 100644
--- a/scripts/vscripts/alyxlib/controls/input.lua
+++ b/scripts/vscripts/alyxlib/controls/input.lua
@@ -261,7 +261,7 @@ end
---Get the name of a hand.
---
---@param hand CPropVRHand|0|1 # The hand entity or ID
----@param use_operant boolean? # If true, name will use primary/secondary instead of right/left
+---@param use_operant? boolean # If true, name will use primary/secondary instead of right/left
function Input:GetHandName(hand, use_operant)
if type(hand) ~= "number" then
hand = hand:GetHandID()
diff --git a/scripts/vscripts/alyxlib/player/events.lua b/scripts/vscripts/alyxlib/player/events.lua
index 43529a3..3ffde77 100644
--- a/scripts/vscripts/alyxlib/player/events.lua
+++ b/scripts/vscripts/alyxlib/player/events.lua
@@ -573,7 +573,7 @@ ListenToGameEvent("player_stored_item_in_itemholder", listenEventPlayerStoredIte
---@class PlayerEventPlayerRemovedItemFromItemholder : GameEventPlayerRemovedItemFromItemholder
---@field item EntityHandle # The entity handle of the item that removed
---@field item_class string # Classname of the entity that was removed
----@field hand CPropVRHand # Hand that the entity was removed form
+---@field hand CPropVRHand # Hand that the entity was removed from
---Tracking wrist items
---@param data GameEventPlayerRemovedItemFromItemholder
From deb88634f093d79095e6e3b5b3f9fb99f500c371 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Sun, 9 Nov 2025 00:17:48 +1300
Subject: [PATCH 106/146] refactor: debug menu available to all users
---
scripts/vscripts/alyxlib/debug/debug_menu.lua | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index f1711a4..fee83bf 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -1002,7 +1002,9 @@ function DebugMenu:StopListeningForMenuActivation()
Player:SetContextThink("debug_menu_activate", nil, 0)
end
-if Convars:GetInt("developer") > 0 then
+-- Developer condition has been "disabled"
+-- All players can now open the menu
+if Convars:GetInt("developer") > -1 then
ListenToPlayerEvent("vr_player_ready", function()
-- Kill existing panel on load to avoid missing logic errors
local panel = Entities:FindByName(nil, "alyxlib_debug_menu")
From 962a7b613e9d84981997d54f522dfdcdef20166f Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Sun, 9 Nov 2025 00:18:58 +1300
Subject: [PATCH 107/146] feat: all save types take nil
---
scripts/vscripts/alyxlib/storage.lua | 45 +++++++++++++++++++++++-----
1 file changed, 37 insertions(+), 8 deletions(-)
diff --git a/scripts/vscripts/alyxlib/storage.lua b/scripts/vscripts/alyxlib/storage.lua
index faa8561..657d759 100644
--- a/scripts/vscripts/alyxlib/storage.lua
+++ b/scripts/vscripts/alyxlib/storage.lua
@@ -152,7 +152,7 @@ end
---
---@param handle EntityHandle # Entity to save on
---@param name string # Name to save as
----@param value number # Number to save
+---@param value number|nil # Number to save
---@return boolean # If the save was successful
function Storage.SaveNumber(handle, name, value)
handle = resolveHandle(handle)
@@ -160,6 +160,13 @@ function Storage.SaveNumber(handle, name, value)
Warn("Invalid save handle ("..tostring(handle)..")!")
return false
end
+
+ if value == nil then
+ handle:SetContext(name, nil, 0)
+ handle:SetContext(name..separator.."type", nil, 0)
+ return true
+ end
+
handle:SetContextNum(name, value, 0)
handle:SetContext(name..separator.."type", "number", 0)
return true
@@ -170,7 +177,7 @@ end
---
---@param handle EntityHandle # Entity to save on
---@param name string # Name to save as
----@param bool boolean # Boolean to save
+---@param bool boolean|nil # Boolean to save
---@return boolean # If the save was successful
function Storage.SaveBoolean(handle, name, bool)
handle = resolveHandle(handle)
@@ -178,6 +185,13 @@ function Storage.SaveBoolean(handle, name, bool)
Warn("Invalid save handle ("..tostring(handle)..")!")
return false
end
+
+ if bool == nil then
+ handle:SetContext(name, nil, 0)
+ handle:SetContext(name..separator.."type", nil, 0)
+ return true
+ end
+
handle:SetContextNum(name, bool and 1 or 0, 0)
handle:SetContext(name..separator.."type", "boolean", 0)
return true
@@ -188,7 +202,7 @@ end
---
---@param handle EntityHandle # Entity to save on
---@param name string # Name to save as
----@param vector Vector # Vector to save
+---@param vector Vector|nil # Vector to save
---@return boolean # If the save was successful
function Storage.SaveVector(handle, name, vector)
handle = resolveHandle(handle)
@@ -196,6 +210,13 @@ function Storage.SaveVector(handle, name, vector)
Warn("Invalid save handle ("..tostring(handle)..")!")
return false
end
+
+ if vector == nil then
+ handle:SetContext(name, nil, 0)
+ handle:SetContext(name..separator.."type", nil, 0)
+ return true
+ end
+
handle:SetContext(name, "", 0)
handle:SetContext(name..separator.."type", "vector", 0)
Storage.SaveNumber(handle, name..separator.."x", vector.x)
@@ -209,7 +230,7 @@ end
---
---@param handle EntityHandle # Entity to save on
---@param name string # Name to save as
----@param qangle QAngle # QAngle to save
+---@param qangle QAngle|nil # QAngle to save
---@return boolean # If the save was successful
function Storage.SaveQAngle(handle, name, qangle)
handle = resolveHandle(handle)
@@ -217,6 +238,13 @@ function Storage.SaveQAngle(handle, name, qangle)
Warn("Invalid save handle ("..tostring(handle)..")!")
return false
end
+
+ if qangle == nil then
+ handle:SetContext(name, nil, 0)
+ handle:SetContext(name..separator.."type", nil, 0)
+ return true
+ end
+
handle:SetContext(name, "", 0)
handle:SetContext(name..separator.."type", "qangle", 0)
Storage.SaveNumber(handle, name..separator.."x", qangle.x)
@@ -304,10 +332,10 @@ function Storage.SaveEntity(handle, name, entity, useClassname)
end
if entity == nil then
- Storage.SaveString(handle, name..separator.."targetname", "")
- Storage.SaveString(handle, name..separator.."classname", "")
- handle:SetContext(name..separator.."unique", "", 0)
- handle:SetContext(name..separator.."type", "entity", 0)
+ Storage.SaveString(handle, name..separator.."targetname", nil)
+ Storage.SaveString(handle, name..separator.."classname", nil)
+ handle:SetContext(name..separator.."unique", nil, 0)
+ handle:SetContext(name..separator.."type", nil, 0)
return true
elseif not entity or not IsValidEntity(entity) then
return false
@@ -376,6 +404,7 @@ end
function Storage.LoadString(handle, name, default)
handle = resolveHandle(handle)
local t = handle:GetContext(name..separator.."type")
+
if t == "string" then
local value = handle:GetContext(name)
if value == nil then return default end
From 886e021960eac4b254bfdab6db5c071f35a575a8 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Sun, 9 Nov 2025 00:23:15 +1300
Subject: [PATCH 108/146] feat: hand and kind given to analog callback
ListenToAnalog now correctly handles hand type and passes these values
into the callback parameter.
---
scripts/vscripts/alyxlib/controls/input.lua | 31 +++++++++++++++------
1 file changed, 22 insertions(+), 9 deletions(-)
diff --git a/scripts/vscripts/alyxlib/controls/input.lua b/scripts/vscripts/alyxlib/controls/input.lua
index 100bc67..971c01e 100644
--- a/scripts/vscripts/alyxlib/controls/input.lua
+++ b/scripts/vscripts/alyxlib/controls/input.lua
@@ -351,7 +351,7 @@ local function getCorrectAnalogValue(analogValue)
end
---
----Listens to a specific analog value reaching a certain value.
+---Listens to a specific analog action reaching a certain value.
---
---@param kind "up"|"down" # `up` means listen for the value moving above `analogValue`, `down` means listen for it moving below
---@param hand CPropVRHand|InputHandKind # The hand entity or kind of hand to listen on
@@ -359,21 +359,32 @@ end
---@param analogValue AnalogValueType # The value(s) to listen for
---@param callback fun(params:InputAnalogCallback) # The function that will be called when conditions are met
---@param context? any # Optional context passed into the callback as the first value
+---@return integer # The callback ID
+---@return integer|nil # The second callback ID, if `hand` is `InputHandBoth`
function Input:ListenToAnalog(kind, hand, analogAction, analogValue, callback, context)
- local handid = convertHandKindToHandId(hand)
+ if type(hand) ~= "number" then
+ hand = hand:GetHandID()
+ -- Quick way to register both hands.
+ elseif hand == -1 then
+ local id1 = self:ListenToAnalog(kind, 0, analogAction, analogValue, callback, context)
+ local id2 = self:ListenToAnalog(kind, 1, analogAction, analogValue, callback, context)
+ return id1, id2
+ end
+
+ local handid = convertHandKindToHandId(hand)
local value = getCorrectAnalogValue(analogValue)
callbackId = callbackId + 1
analogCallbacks[callbackId] = {
- checkGreaterThan = kind == "up",
- actualhandid = handid,
func = callback,
context = context,
- literalhandtype = 1 - handid,
+ handkind = hand,
+ actualhandid = handid,
analog = analogAction,
+ checkGreaterThan = kind == "up",
+ literalhandtype = 1 - handid,
value = value,
- handkind = hand
}
return callbackId
@@ -467,7 +478,7 @@ end
---A callback for when a button is pressed.
---
---@class InputPressCallback
----@field kind "press" # The kind of event
+---@field kind "press" # The kind of action event
---@field press_time number # The server time at which the button was pressed
---@field hand CPropVRHand # EntityHandle for the hand that pressed the button
---@field button DigitalInputAction # The ID of the button that was pressed
@@ -476,7 +487,7 @@ end
---A callback for when a button is released.
---
---@class InputReleaseCallback
----@field kind "release" # The kind of event
+---@field kind "release" # The kind of action event
---@field release_time number # The server time at which the button was released
---@field hand CPropVRHand # EntityHandle for the hand that released the button
---@field button DigitalInputAction # The ID of the button that was pressed
@@ -486,6 +497,7 @@ end
---A callback for when an analog action is moved.
---
---@class InputAnalogCallback
+---@field kind "up"|"down" # The kind of analog event
---@field value Vector # The vector value of the analog action at the time of detection
---@field hand CPropVRHand # EntityHandle for the hand that moved the analog action
---@field analog AnalogInputAction # The ID of the analog action that was moved
@@ -593,7 +605,8 @@ local function InputThink()
local t = {
value = value,
analog = analogData.analog,
- hand = hmd:GetVRHand(analogData.actualhandid)
+ hand = hmd:GetVRHand(analogData.actualhandid),
+ kind = analogData.checkGreaterThan and "up" or "down",
}
if analogData.context then
analogData.func(analogData.context, t)
From 5053c2e22d292c95549445f5715bba6a10c3f4c1 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Sun, 9 Nov 2025 00:24:39 +1300
Subject: [PATCH 109/146] docs: include ListenToButton return types
---
scripts/vscripts/alyxlib/controls/input.lua | 2 ++
1 file changed, 2 insertions(+)
diff --git a/scripts/vscripts/alyxlib/controls/input.lua b/scripts/vscripts/alyxlib/controls/input.lua
index 971c01e..9a5fd23 100644
--- a/scripts/vscripts/alyxlib/controls/input.lua
+++ b/scripts/vscripts/alyxlib/controls/input.lua
@@ -292,6 +292,8 @@ end
---@param presses integer|nil # Number of times the button must be pressed in quick succession. E.g. 2 for double click. Only applicable for `kind` press
---@param callback fun(params:InputPressCallback|InputReleaseCallback)|fun(context:T,params:InputPressCallback|InputReleaseCallback) # The function that will be called when the button is pressed
---@param context? T # Optional context passed into the callback as the first value
+---@return integer # The callback ID
+---@return integer|nil # The second callback ID, if `hand` is `InputHandBoth`
function Input:ListenToButton(kind, hand, button, presses, callback, context)
if type(hand) ~= "number" then
From 8f01499d579dfe0a2162f5a77879d6af32c88fd6 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Sun, 9 Nov 2025 00:25:39 +1300
Subject: [PATCH 110/146] refactor: rename INPUT_CALLBACK
---
scripts/vscripts/alyxlib/controls/input.lua | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/scripts/vscripts/alyxlib/controls/input.lua b/scripts/vscripts/alyxlib/controls/input.lua
index 9a5fd23..9ee8a07 100644
--- a/scripts/vscripts/alyxlib/controls/input.lua
+++ b/scripts/vscripts/alyxlib/controls/input.lua
@@ -504,7 +504,7 @@ end
---@field hand CPropVRHand # EntityHandle for the hand that moved the analog action
---@field analog AnalogInputAction # The ID of the analog action that was moved
----@alias INPUT_CALLBACK InputPressCallback|InputReleaseCallback
+---@alias InputGenericCallback InputPressCallback|InputReleaseCallback
local function InputThink()
---@TODO remove these variables and use cached literal/hand entity
From 89a101ddeeba1cdd060eb933071aaab7bd06d723 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Sun, 9 Nov 2025 00:26:03 +1300
Subject: [PATCH 111/146] refactor: use devprint for input starting
---
scripts/vscripts/alyxlib/controls/input.lua | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/scripts/vscripts/alyxlib/controls/input.lua b/scripts/vscripts/alyxlib/controls/input.lua
index 9ee8a07..a743f65 100644
--- a/scripts/vscripts/alyxlib/controls/input.lua
+++ b/scripts/vscripts/alyxlib/controls/input.lua
@@ -637,7 +637,7 @@ function Input:Start(on)
if on == nil then on = Entities:GetLocalPlayer() end
tracking_ent = on
tracking_ent:SetContextThink("InputThink", InputThink, 0)
- print("Input system starting...")
+ devprint("Input system starting...")
end
---
From aede859b6186dd6e25f58ef68284adecc0ebeab6 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Thu, 13 Nov 2025 23:48:42 +1300
Subject: [PATCH 112/146] feat: add debug menu convar dumps
debug_menu_generate_cfg prints a list that can be pasted directly into a cfg file.
debug_menu_dump_convars dumps all linked convars without any formatting.
DebugMenu:GetLinkedConvars gathers convar data for the commands.
---
scripts/vscripts/alyxlib/debug/debug_menu.lua | 83 +++++++++++++++++++
1 file changed, 83 insertions(+)
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index fee83bf..23ba6ce 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -42,6 +42,39 @@ RegisterAlyxLibConvar("debug_menu_lock", "0", "Prevents the debug menu from bein
RegisterAlyxLibConvar("debug_menu_extras", "0", "Enable the extras tab by default", 0)
+RegisterAlyxLibCommand("debug_menu_dump_convars", function()
+ local categories = DebugMenu:GetLinkedConvars()
+ for _, category in pairs(categories) do
+ for _, convar in pairs(category.convars) do
+ Msg(string.format("%s : %s\n", convar.name, convar.value))
+ end
+ end
+end, "Dumps all convars tied to the debug menu and their values to the console", 0)
+
+RegisterAlyxLibCommand("debug_menu_generate_cfg", function ()
+ -- By collecting first we avoid warnings printed throughout the list
+
+ local categories = DebugMenu:GetLinkedConvars()
+
+ Msg("\n// Copy and paste the following into debug_menu.cfg to save your settings\n\n")
+
+ for _, category in pairs(categories) do
+ Msg("// " .. category.categoryName .. "\n")
+ for _, convar in pairs(category.convars) do
+ if convar.value ~= nil then
+ local defaultStr = ""
+ if convar.default ~= nil then
+ defaultStr = (convar.default ~= convar.value) and ("// default: "..convar.default.."") or ""
+ end
+ local valStr = convar.value or ""
+ local str = string.format("%-" .. category.maxLengthConvar .. "s %-" .. category.maxLengthValue .. "s %s\n", convar.name, valStr, defaultStr)
+ Msg(str)
+ end
+ end
+ Msg("\n")
+ end
+end, "Prints the current Debug Menu settings in cfg format which can be pasted into any cfg file", 0)
+
---
---The debug menu allows for easier VR testing by offering a customizable in-game menu.
---
@@ -955,6 +988,56 @@ function DebugMenu:SetItemVisibilityCondition(categoryId, itemId, condition)
item.condition = condition
end
+---
+---A category of convars dumped by `DebugMenu:GetLinkedConvars`.
+---
+---@class DebugMenuDumpedCategory
+---@field categoryName string # The name of the category
+---@field maxLengthConvar number # The length of the longest convar name
+---@field maxLengthValue number # The length of the longest convar value
+---@field convars {name:string, value:string?, default:string?}[] # The convars in this category
+
+---
+---Gets a list of all convars linked to debug menu items.
+---
+---@return DebugMenuDumpedCategory[] # A list of all convars
+function DebugMenu:GetLinkedConvars()
+ ---@type DebugMenuDumpedCategory[]
+ local categories = {}
+
+ for _,category in pairs(DebugMenu.categories) do
+ local convars = {}
+ local dumpedCategory = {categoryName = category.name, maxLengthConvar = 0, maxLengthValue = 0, convars = convars}
+ table.insert(categories, dumpedCategory)
+ for _,item in pairs(category.items) do
+ local convar = item.convar
+ if convar ~= nil then
+ dumpedCategory.maxLengthConvar = math.max(dumpedCategory.maxLengthConvar, #convar)
+ if convar and convar ~= "" then
+
+ local default = nil
+ local val = nil
+
+ if EasyConvars:Exists(convar) then
+ default = tostring(EasyConvars:GetConvarData(convar).defaultValue)
+ val = EasyConvars:GetStr(convar)
+ else
+ val = Convars:GetStr(convar)
+ end
+
+ if val ~= nil then
+ dumpedCategory.maxLengthValue = math.max(dumpedCategory.maxLengthValue, #val)
+ end
+
+ table.insert(convars, {name = convar, value = val, default = default})
+ end
+ end
+ end
+ end
+
+ return categories
+end
+
---
---Starts listening for the debug menu activation button.
---
From edfd65ed65d65dc4913e390f779db1ba4401fd64 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Thu, 13 Nov 2025 23:50:27 +1300
Subject: [PATCH 113/146] fix: panel update error in novr
---
scripts/vscripts/alyxlib/debug/debug_menu.lua | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index 23ba6ce..e83eb32 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -361,6 +361,11 @@ local debugPanelScriptScope = {
---Parenting causes stutters so it must be calculated manually.
---@param panel EntityHandle
local function updatePanelWithAnchorParent(panel)
+ ---@TODO Unsure best way to follow moving non vr player
+ if not Player.HMDAnchor then
+ return
+ end
+
---@type EntityHandle?
local currentAnchorParent = nil
local anchorRelativeTransform = nil
From 6f3af9046177f582e0c51253f58a750f9f1b847a Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Thu, 13 Nov 2025 23:53:37 +1300
Subject: [PATCH 114/146] feat: new debug menu settings panel
Has a dedicated button at the top of the menu.
---
.../layout/custom_game/alyxlib_debug_menu.xml | 12 ++++--
.../scripts/custom_game/alyxlib_debug_menu.js | 37 ++++++++++++++++---
.../styles/custom_game/alyxlib_debug_menu.css | 29 +++++++++++++++
scripts/vscripts/alyxlib/debug/debug_menu.lua | 9 +++++
4 files changed, 78 insertions(+), 9 deletions(-)
diff --git a/panorama/layout/custom_game/alyxlib_debug_menu.xml b/panorama/layout/custom_game/alyxlib_debug_menu.xml
index ead77a5..f4131e9 100644
--- a/panorama/layout/custom_game/alyxlib_debug_menu.xml
+++ b/panorama/layout/custom_game/alyxlib_debug_menu.xml
@@ -29,10 +29,14 @@
-
+
+
+
+
diff --git a/panorama/scripts/custom_game/alyxlib_debug_menu.js b/panorama/scripts/custom_game/alyxlib_debug_menu.js
index d42f721..2d75342 100644
--- a/panorama/scripts/custom_game/alyxlib_debug_menu.js
+++ b/panorama/scripts/custom_game/alyxlib_debug_menu.js
@@ -5,6 +5,8 @@
let panelReady = false;
+let settingsCategory = null;
+
/**
* Fires a Panorama output with the given name and arguments.
* The output is routed to the panel's input 'RunScriptCode'.
@@ -24,7 +26,7 @@ function FireOutput(outputName, ...args) {
}
/**
- * Maps category id to category object.
+ * List of all categories.
* @type {Category[]}
*/
let categories = [];
@@ -136,7 +138,10 @@ class Category
this.panel.AddClass("scroll");
this.content = $.CreatePanel("Panel", this.panel, `${this.id}_content`);
this.content.AddClass("content");
+ }
+ AddToCategoryBar()
+ {
// Create category button
this.button = CreateDebugMenuButton($("#CategoryBar"), () => SetCategoryVisible(this.id), "CategoryButton", `${this.id}_button`);
@@ -174,17 +179,21 @@ class Category
if (visible)
{
this.panel.AddClass("Visible");
- this.button.AddClass("Selected");
+ if (this.button != null)
+ this.button.AddClass("Selected");
}
else
{
this.panel.RemoveClass("Visible");
- this.button.RemoveClass("Selected");
+ if (this.button != null)
+ this.button.RemoveClass("Selected");
}
}
SetBarButtonVisible(visible)
{
+ if (this.button == null) return;
+
if (visible)
this.button.visible = true;
else
@@ -821,6 +830,9 @@ class SubMenuLabel
*/
function SetCategoryVisible(id)
{
+ // Hide settings if available
+ if (settingsCategory) settingsCategory.SetVisible(false);
+
for (const category of categories)
{
if (category.id == id)
@@ -893,14 +905,19 @@ function CreateCategory(id, name)
{
let category = new Category(id, name);
- categories.push(category);
+ if (id === "settings") {
+ settingsCategory = category;
+ } else {
+ category.AddToCategoryBar();
+ categories.push(category);
+ }
// Restore selected tab on refresh
if ($.GetContextPanel().currentlySelectedCategory) {
if ($.GetContextPanel().currentlySelectedCategory == category.id) {
SetCategoryVisible(category.id);
}
- } else if (currentlySelectedCategory == null) {
+ } else if (currentlySelectedCategory == null && category !== settingsCategory) {
SetCategoryVisible(category.id)
}
@@ -916,6 +933,8 @@ function CreateCategory(id, name)
*/
function GetCategory(id)
{
+ if (id === "settings") return settingsCategory;
+
for (const category of categories)
{
if (category.id == id) return category;
@@ -938,6 +957,14 @@ function StartPanelDrag()
FireOutput("_DebugMenuDrag");
}
+function ShowSettings()
+{
+ if (settingsCategory) {
+ SetCategoryVisible();
+ settingsCategory.SetVisible(true);
+ }
+}
+
/**
* Gets the position of the left or right 'affordance' circle for the VR finger interacting with the menu.
* @returns {{x:number,y:number}?}
diff --git a/panorama/styles/custom_game/alyxlib_debug_menu.css b/panorama/styles/custom_game/alyxlib_debug_menu.css
index 7506fa3..311e601 100644
--- a/panorama/styles/custom_game/alyxlib_debug_menu.css
+++ b/panorama/styles/custom_game/alyxlib_debug_menu.css
@@ -406,4 +406,33 @@ Label.custom_label
transition-property: none;
transition-duration: 0s;
transition-timing-function: ease-in;
+}
+
+.TopBar
+{
+ flow-children: right;
+ width: 100%;
+ margin-bottom: 10px;
+}
+
+#SettingsButton
+{
+ background-image: url("s2r://panorama/images/game_menu_ui/options_advanced_png.vtex");
+ background-repeat: no-repeat;
+ background-size: cover;
+ background-position: center;
+ width: 80px;
+ /* margin-top: 24px; */
+ margin-left: 24px;
+ margin-right: 24px;
+ /* padding-top: 8px; */
+ /* background-color: #FFBE5533; */
+ border: 2px solid #FFBE55;
+ horizontal-align: left;
+}
+
+#SettingsButton:hover
+{
+ background-image: url("s2r://panorama/images/game_menu_ui/options_advanced_hover_png.vtex");
+ background-color: #FFBE5588;
}
\ No newline at end of file
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index e83eb32..ab16027 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -1106,6 +1106,15 @@ if Convars:GetInt("developer") > -1 then
end, nil)
end
+--[[
+ Settings tab
+]]
+DebugMenu:AddCategory("settings", "")
+DebugMenu:AddToggle("settings", "menu_hand", "Menu on primary hand", "debug_menu_hand")
+DebugMenu:AddToggle("settings", "menu_floating", "Menu floating", "debug_menu_floating")
+DebugMenu:AddToggle("settings", "menu_lock", "Menu locked", "debug_menu_lock")
+DebugMenu:AddToggle("settings", "menu_extras", "Menu extras", "debug_menu_extras")
+DebugMenu:AddSlider("settings", "menu_height", "Menu height", "debug_menu_height", 7, 30, false, 2)
--[[
Default AlyxLib tab
From 634b673e7fc7426b46ddfa17e642e8b0c8e6eb71 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Thu, 13 Nov 2025 23:54:10 +1300
Subject: [PATCH 115/146] fix: cycle highlight icon not changing
---
panorama/layout/custom_game/alyxlib_debug_menu.xml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/panorama/layout/custom_game/alyxlib_debug_menu.xml b/panorama/layout/custom_game/alyxlib_debug_menu.xml
index f4131e9..ffba1f9 100644
--- a/panorama/layout/custom_game/alyxlib_debug_menu.xml
+++ b/panorama/layout/custom_game/alyxlib_debug_menu.xml
@@ -40,7 +40,7 @@
-
diff --git a/panorama/scripts/custom_game/alyxlib_debug_menu.js b/panorama/scripts/custom_game/alyxlib_debug_menu.js
index 97783a4..da206f5 100644
--- a/panorama/scripts/custom_game/alyxlib_debug_menu.js
+++ b/panorama/scripts/custom_game/alyxlib_debug_menu.js
@@ -488,7 +488,7 @@ class SubMenuSlider
// This only works in VR
TurnButtonIntoDebugMenuButton(this.panel, () => {
- const pos = GetAffordancePosition();
+ const pos = GetAffordancePositionInMenuSpace();
if (pos !== null) {
// Slider does not have actualxoffset or actuallayoutwidth THANKS AGAIN VALVE
// These magic numbers are estimates of where the slider is in relation to the parent panel
@@ -962,6 +962,7 @@ function ShowSettings()
if (settingsCategory) {
SetCategoryVisible();
settingsCategory.SetVisible(true);
+ currentlySelectedCategory = settingsCategory;
}
}
@@ -981,6 +982,23 @@ function GetAffordancePosition() {
return null;
}
+/**
+ * Gets the position of the left or right 'affordance' circle for the VR finger interacting with the menu,
+ * normalized to the menu container.
+ * @returns {{x:number,y:number}?}
+ */
+function GetAffordancePositionInMenuSpace() {
+ const affordancePosition = GetAffordancePosition();
+ if (affordancePosition) {
+ let bgPanel = $("#background_panel");
+ return {
+ x: affordancePosition.x - bgPanel.actualxoffset,
+ y: affordancePosition.y - bgPanel.actualyoffset
+ };
+ }
+ return null;
+}
+
/**
* Remaps a number from one range to another.
* @param {number} value - The input value to remap.
@@ -1032,12 +1050,41 @@ function ClickHoveredButton()
}
/**
- * Sets the height of the menu container.
- * @param {number} height
+ * Sets the width and height of the menu container.
+ * @param {number?} width
+ * @param {number?} height
*/
-function SetContainerHeight(height)
-{
- $('#CategoriesContainer').style.minHeight = `${height}px`;
+function SetContainerSize(width, height) {
+ // Clear container clip so larger sizes don't get clipped before the schedule
+ var container = $("#affordance_container");
+ container.style.clip = "rect(0px, 100%, 100%, 0px)";
+
+ let bgPanel = $("#background_panel");
+
+ if (width)
+ bgPanel.style.width = `${width}px`;
+ if (height) {
+ bgPanel.style.height = `${height}px`;
+ $('#CategoriesContainer').style.minHeight = `${height - 111}px`;
+ }
+
+ // Wait for element update and any animations
+ // 0.6s is the duration of the opening animation
+ $.Schedule(0.61, ()=> {
+ var bgWidth = bgPanel.actuallayoutwidth;
+ var bgHeight = bgPanel.actuallayoutheight;
+ var containerWidth = container.actuallayoutwidth;
+ var containerHeight = container.actuallayoutheight;
+
+ // Calculate centered x position within container
+ var x = (containerWidth - bgWidth) / 2;
+ // Calculate the bottom y position
+ var y = (containerHeight - bgHeight);
+
+ var clipRect = "rect(" + y + "px, " + (x + bgWidth) + "px, " + (y + bgHeight) + "px, " + x + "px)";
+
+ container.style.clip = clipRect;
+ });
}
/**
@@ -1207,9 +1254,10 @@ function ParseCommand(command, args)
break;
}
- case "setheight": {
- const height = parseInt(args[0]);
- SetContainerHeight(height);
+ case "setsize": {
+ const width = parseInt(args[0]);
+ const height = parseInt(args[1]);
+ SetContainerSize(width, height);
break;
}
}
@@ -1271,6 +1319,7 @@ function ScrollHelperClick() {
{
// Modify preset layout buttons to work with controller trigger
TurnButtonIntoDebugMenuButton($("#CloseMenuButton"));
+ TurnButtonIntoDebugMenuButton($("#SettingsButton"));
TurnButtonIntoDebugMenuButton($("#TitleBar"));
TurnButtonIntoDebugMenuButton($("#CycleCategoryLeftButton"));
TurnButtonIntoDebugMenuButton($("#CycleCategoryRightButton"));
diff --git a/panorama/styles/custom_game/alyxlib_debug_menu.css b/panorama/styles/custom_game/alyxlib_debug_menu.css
index 311e601..4008de0 100644
--- a/panorama/styles/custom_game/alyxlib_debug_menu.css
+++ b/panorama/styles/custom_game/alyxlib_debug_menu.css
@@ -7,11 +7,15 @@
box-shadow: inset #995e0011 2px 2px 4px 4px;
opacity: .9;
height: 100%;
+ horizontal-align: center;
+ vertical-align: bottom;
}
.vr_affordance_container{
+ width: 100%;
height: 100%;
}
#layout_root{
+ width: 100%;
height: 100%;
}
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index 3be71f1..ff6d7c5 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -9,11 +9,15 @@
require "alyxlib.debug.debug_menu"
]]
+local function updateAttachment()
+ DebugMenu:UpdateMenuAttachment()
+end
+
RegisterAlyxLibCommand("debug_menu_show", function (name, ...)
DebugMenu:ShowMenu()
end, "Forces the debug menu to show")
-RegisterAlyxLibConvar("debug_menu_hand", "1", "Hand to attach the debug menu to, 0 = Secondary : 1 = Primary")
+RegisterAlyxLibConvar("debug_menu_hand", "1", "Hand to attach the debug menu to, 0 = Secondary : 1 = Primary", 0, updateAttachment)
RegisterAlyxLibConvar("debug_menu_offset_x", "-3", "X offset of the debug menu", 0)
RegisterAlyxLibConvar("debug_menu_offset_y", "-5", "Y offset of the debug menu", 0)
@@ -22,11 +26,11 @@ RegisterAlyxLibConvar("debug_menu_offset_pitch", "-14", "Pitch offset of the deb
RegisterAlyxLibConvar("debug_menu_offset_yaw", "-172", "Yaw offset of the debug menu", 0)
RegisterAlyxLibConvar("debug_menu_offset_roll", "-2", "Roll offset of the debug menu", 0)
+RegisterAlyxLibConvar("debug_menu_width", "15", "Width of the debug menu, min=10 : max=30", 0, function(newVal, oldVal)
+ DebugMenu:SetSize(Convars:GetInt("debug_menu_width"), nil)
+end)
RegisterAlyxLibConvar("debug_menu_height", "14", "Height of the debug menu, min=7 : max=30", 0, function(newVal, oldVal)
- if DebugMenu:IsOpen() then
- DebugMenu:CloseMenu()
- DebugMenu:ShowMenu()
- end
+ DebugMenu:SetSize(nil, Convars:GetInt("debug_menu_height"))
end)
RegisterAlyxLibConvar("debug_menu_floating", function()
@@ -36,7 +40,7 @@ RegisterAlyxLibConvar("debug_menu_floating", function()
end
return false
-end, "Menu will float in world instead of attached to hand")
+end, "Menu will float in world instead of attached to hand", 0, updateAttachment)
RegisterAlyxLibConvar("debug_menu_lock", "0", "Prevents the debug menu from being repositioned by the player", 0)
@@ -75,6 +79,18 @@ RegisterAlyxLibCommand("debug_menu_generate_cfg", function ()
end
end, "Prints the current Debug Menu settings in cfg format which can be pasted into any cfg file", 0)
+---DPI of the debug menu panel.
+local DPI = 64
+
+---Minimum and maximum sizes of the debug menu panel.
+local MIN_WIDTH = 10
+local MIN_HEIGHT = 7
+local MAX_WIDTH = 30
+local MAX_HEIGHT = 30
+
+---Attachment name that the panel attaches to on the hand.
+local handAttachment = ""
+
---
---The debug menu allows for easier VR testing by offering a customizable in-game menu.
---
@@ -200,7 +216,6 @@ end
local function startDraggingMenu(dragHand)
local panel = DebugMenu.panel
- local dragent = dragHand
local relativeTransform = TransformToLocal(dragHand, panel:GetOrigin(), panel:GetAngles())
@@ -218,7 +233,7 @@ local function startDraggingMenu(dragHand)
end
local hand = Convars:GetBool("debug_menu_hand") and Player.PrimaryHand or Player.SecondaryHand
- panel:SetParent(hand, "constraint1")
+ panel:SetParent(hand, handAttachment)
local localOrigin = panel:GetLocalOrigin()
local localAngles = panel:GetLocalAngles()
@@ -471,14 +486,14 @@ function DebugMenu:ShowMenu()
self.panel = SpawnEntityFromTableSynchronous("point_clientui_world_panel", {
targetname = "alyxlib_debug_menu",
dialog_layout_name = "file://{resources}/layout/custom_game/alyxlib_debug_menu.xml",
- width = 16,
- height = Convars:GetInt("debug_menu_height"),
- panel_dpi = 64,
+ width = 30,
+ height = 30,
+ panel_dpi = DPI,
ignore_input = 0,
lit = 0,
interact_distance = 64,
- vertical_align = "1",
+ vertical_align = "0",
-- orientation = "0",
horizontal_align = "1",
@@ -499,9 +514,10 @@ function DebugMenu:ShowMenu()
debugMenuOpen = true
- local panelHeight = Clamp(Convars:GetInt("debug_menu_height"), 7, 30)
- local cssHeight = (64 * panelHeight) - 111
- Panorama:Send(self.panel, "SetHeight", cssHeight)
+ self:SetSize(
+ Convars:GetInt("debug_menu_width"),
+ Convars:GetInt("debug_menu_height")
+ )
self:SendCategoriesToPanel()
end
@@ -859,6 +875,27 @@ function DebugMenu:SetCategoryIndex(categoryId, index)
end
end
+---
+---Sets the size of the debug menu panel if it's open.
+---
+---This does not change any convars and is not persisted.
+---
+---@param width? number # Width in "panel units"
+---@param height? number # Height in "panel units"
+function DebugMenu:SetSize(width, height)
+ if width then
+ width = Clamp(width, MIN_WIDTH, MAX_WIDTH) * DPI
+ end
+
+ if height then
+ height = Clamp(height, MIN_HEIGHT, MAX_HEIGHT) * DPI
+ end
+
+ if self.panel then
+ Panorama:Send(self.panel, "SetSize", width, height)
+ end
+end
+
---Resolves the default value of an element by running any value getter functions.
---@param item DebugMenuItem # The item to resolve
---@param tFunc? `Convars.GetStr`|`Convars.GetInt`|`Convars.GetFloat`|`Convars.GetBool` # The value getter function
@@ -1130,7 +1167,8 @@ DebugMenu:AddToggle("settings", "menu_hand", "Menu on primary hand", "debug_menu
DebugMenu:AddToggle("settings", "menu_floating", "Menu floating", "debug_menu_floating")
DebugMenu:AddToggle("settings", "menu_lock", "Menu locked", "debug_menu_lock")
DebugMenu:AddToggle("settings", "menu_extras", "Menu extras", "debug_menu_extras")
-DebugMenu:AddSlider("settings", "menu_height", "Menu height", "debug_menu_height", 7, 30, false, 2)
+DebugMenu:AddSlider("settings", "menu_width", "Menu width", "debug_menu_width", 10, 30, false, 0)
+DebugMenu:AddSlider("settings", "menu_height", "Menu height", "debug_menu_height", 7, 30, false, 0)
--[[
Default AlyxLib tab
From de07a639d614e37ff2604678d95bfcec968d0cce Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Fri, 21 Nov 2025 03:23:03 +1300
Subject: [PATCH 126/146] feat: add generate cfg button to settings
---
scripts/vscripts/alyxlib/debug/debug_menu.lua | 2 ++
1 file changed, 2 insertions(+)
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index ff6d7c5..db0a603 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -1169,6 +1169,8 @@ DebugMenu:AddToggle("settings", "menu_lock", "Menu locked", "debug_menu_lock")
DebugMenu:AddToggle("settings", "menu_extras", "Menu extras", "debug_menu_extras")
DebugMenu:AddSlider("settings", "menu_width", "Menu width", "debug_menu_width", 10, 30, false, 0)
DebugMenu:AddSlider("settings", "menu_height", "Menu height", "debug_menu_height", 7, 30, false, 0)
+DebugMenu:AddLabel("settings", "", "Print all current menu settings to the console for saving")
+DebugMenu:AddButton("settings", "print_settings", "Print Settings To Console", "debug_menu_generate_cfg")
--[[
Default AlyxLib tab
From ca630479f54be33a4980fe8b2af38a49e4aa57a0 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Fri, 21 Nov 2025 03:26:04 +1300
Subject: [PATCH 127/146] refactor: return full category in dump
---
scripts/vscripts/alyxlib/debug/debug_menu.lua | 21 ++++++++++++-------
1 file changed, 14 insertions(+), 7 deletions(-)
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index db0a603..159ccba 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -58,20 +58,24 @@ end, "Dumps all convars tied to the debug menu and their values to the console",
RegisterAlyxLibCommand("debug_menu_generate_cfg", function ()
-- By collecting first we avoid warnings printed throughout the list
- local categories = DebugMenu:GetLinkedConvars()
+ local dumps = DebugMenu:GetLinkedConvars()
Msg("\n// Copy and paste the following into debug_menu.cfg to save your settings\n\n")
- for _, category in pairs(categories) do
- Msg("// " .. category.categoryName .. "\n")
- for _, convar in pairs(category.convars) do
+ for _, dump in pairs(dumps) do
+ local name = dump.category.name
+ if name == nil or name == "" then
+ name = dump.category.id
+ end
+ Msg("// " .. name .. "\n")
+ for _, convar in pairs(dump.convars) do
if convar.value ~= nil then
local defaultStr = ""
if convar.default ~= nil then
defaultStr = (convar.default ~= convar.value) and ("// default: "..convar.default.."") or ""
end
local valStr = convar.value or ""
- local str = string.format("%-" .. category.maxLengthConvar .. "s %-" .. category.maxLengthValue .. "s %s\n", convar.name, valStr, defaultStr)
+ local str = string.format("%-" .. dump.maxLengthConvar .. "s %-" .. dump.maxLengthValue .. "s %s\n", convar.name, valStr, defaultStr)
Msg(str)
end
end
@@ -1050,7 +1054,7 @@ end
---A category of convars dumped by `DebugMenu:GetLinkedConvars`.
---
---@class DebugMenuDumpedCategory
----@field categoryName string # The name of the category
+---@field category DebugMenuCategory # The category that these convars are linked to
---@field maxLengthConvar number # The length of the longest convar name
---@field maxLengthValue number # The length of the longest convar value
---@field convars {name:string, value:string?, default:string?}[] # The convars in this category
@@ -1065,8 +1069,11 @@ function DebugMenu:GetLinkedConvars()
for _,category in pairs(DebugMenu.categories) do
local convars = {}
- local dumpedCategory = {categoryName = category.name, maxLengthConvar = 0, maxLengthValue = 0, convars = convars}
+
+ ---@type DebugMenuDumpedCategory
+ local dumpedCategory = {category = category, maxLengthConvar = 0, maxLengthValue = 0, convars = convars}
table.insert(categories, dumpedCategory)
+
for _,item in pairs(category.items) do
local convar = item.convar
if convar ~= nil then
From dacddc7b1c0f28206d65b52392c071d5a5fd1ae1 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Fri, 21 Nov 2025 03:27:51 +1300
Subject: [PATCH 128/146] refactor: print menu offsets in cfg generation
---
scripts/vscripts/alyxlib/debug/debug_menu.lua | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index 159ccba..44a3f32 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -62,6 +62,15 @@ RegisterAlyxLibCommand("debug_menu_generate_cfg", function ()
Msg("\n// Copy and paste the following into debug_menu.cfg to save your settings\n\n")
+ Msg("// offsets\n")
+ Msg("debug_menu_offset_x " .. math.trunc(Convars:GetFloat("debug_menu_offset_x"), 2) .. "\n")
+ Msg("debug_menu_offset_y " .. math.trunc(Convars:GetFloat("debug_menu_offset_y"), 2) .. "\n")
+ Msg("debug_menu_offset_z " .. math.trunc(Convars:GetFloat("debug_menu_offset_z"), 2) .. "\n")
+ Msg("debug_menu_offset_pitch " .. math.trunc(Convars:GetFloat("debug_menu_offset_pitch"), 2) .. "\n")
+ Msg("debug_menu_offset_yaw " .. math.trunc(Convars:GetFloat("debug_menu_offset_yaw"), 2) .. "\n")
+ Msg("debug_menu_offset_roll " .. math.trunc(Convars:GetFloat("debug_menu_offset_roll"), 2) .. "\n")
+ Msg("\n")
+
for _, dump in pairs(dumps) do
local name = dump.category.name
if name == nil or name == "" then
@@ -81,6 +90,8 @@ RegisterAlyxLibCommand("debug_menu_generate_cfg", function ()
end
Msg("\n")
end
+
+ Msg("// End of debug_menu.cfg\n")
end, "Prints the current Debug Menu settings in cfg format which can be pasted into any cfg file", 0)
---DPI of the debug menu panel.
From 187c06466e32ed1287db7f91c6712b3ad878e3da Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Fri, 21 Nov 2025 03:32:22 +1300
Subject: [PATCH 129/146] feat: dynamically create/remove extras tab
---
scripts/vscripts/alyxlib/debug/debug_menu.lua | 45 +++++++------
.../alyxlib/debug/debug_menu_extras.lua | 66 ++++++++++++++-----
2 files changed, 74 insertions(+), 37 deletions(-)
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index 44a3f32..67d10ec 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -44,7 +44,15 @@ end, "Menu will float in world instead of attached to hand", 0, updateAttachment
RegisterAlyxLibConvar("debug_menu_lock", "0", "Prevents the debug menu from being repositioned by the player", 0)
-RegisterAlyxLibConvar("debug_menu_extras", "0", "Enable the extras tab by default", 0)
+local extras = require("alyxlib.debug.debug_menu_extras")
+
+RegisterAlyxLibConvar("debug_menu_extras", "0", "Enable the extras tab by default", 0, function()
+ if Convars:GetBool("debug_menu_extras") then
+ extras.createTab()
+ else
+ extras.removeTab()
+ end
+end)
RegisterAlyxLibCommand("debug_menu_dump_convars", function()
local categories = DebugMenu:GetLinkedConvars()
@@ -631,6 +639,23 @@ function DebugMenu:AddCategory(id, name)
})
end
+---
+---Removes a category from the debug menu.
+---
+---This does not update the menu if it's already open;
+---use [DebugMenu:Refresh()](lua://DebugMenu.Refresh) to update manually.
+---
+---@param id string # The ID of the category to remove
+function DebugMenu:RemoveCategory(id)
+ local category, index = self:GetCategory(id)
+ if not category then
+ warn("Cannot remove category '"..id.."': Category does not exist!")
+ return
+ end
+
+ table.remove(self.categories, index)
+end
+
---
---Adds a separator line to a category.
---
@@ -1262,22 +1287,4 @@ DebugMenu:AddButton(categoryId, "demo_recording", "Start Recording Demo", functi
end
end)
-if Convars:GetBool("debug_menu_extras") then
- require "alyxlib.debug.debug_menu_extras"
-else
- DebugMenu:AddSeparator(categoryId)
-
- DebugMenu:AddButton(categoryId, "enableextras", "Enable Extras Tab...", function()
- if package.loaded["alyxlib.debug.debug_menu_extras"] == nil then
- require "alyxlib.debug.debug_menu_extras"
- -- Update the panel immediately
- local id = "alyxlib_extras"
- DebugMenu:SendCategoryToPanel(DebugMenu:GetCategory(id))
- DebugMenu:SetCategoryIndex(id, 2)
- ---@TODO Allow disabling extras tab
- DebugMenu:SetItemText(categoryId, "enableextras", "Extras Tab Enabled!")
- end
- end)
-end
-
return DebugMenu.version
\ No newline at end of file
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu_extras.lua b/scripts/vscripts/alyxlib/debug/debug_menu_extras.lua
index 89bdbf6..f92ab6d 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu_extras.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu_extras.lua
@@ -14,30 +14,60 @@ end
local categoryId = "alyxlib_extras"
-DebugMenu:AddCategory(categoryId, "AlyxLib Extras")
+---
+---Creates the AlyxLib Extras tab in the debug menu.
+---
+local function createTab()
+ if DebugMenu:GetCategory(categoryId) then
+ return
+ end
+
+ DebugMenu:AddCategory(categoryId, "AlyxLib Extras")
+
+ DebugMenu:AddSeparator(categoryId, nil, "Display")
+
+ -- Using a callback avoids convar warning in console
+ DebugMenu:AddToggle(categoryId, "showtriggers", "Show Triggers", nil, function(on) SendToConsole("showtriggers " .. (on and 1 or 0)) end)
-DebugMenu:AddSeparator(categoryId, nil, "Display")
+ DebugMenu:AddToggle(categoryId, "luxels", "Mat Luxels", "mat_luxels")
-DebugMenu:AddToggle(categoryId, "showtriggers", "Show Triggers", "showtriggers")
+ DebugMenu:AddToggle(categoryId, "fullbright", "Fullbright", "mat_fullbright")
-DebugMenu:AddToggle(categoryId, "luxels", "Mat Luxels", "mat_luxels")
+ DebugMenu:AddToggle(categoryId, "visibility", "Vis", "vis_enable")
-DebugMenu:AddToggle(categoryId, "fullbright", "Fullbright", "mat_fullbright")
+ DebugMenu:AddToggle(categoryId, "cubemapcolors", "Cubemap Colors", "r_cubemap_debug_colors")
-DebugMenu:AddToggle(categoryId, "visibility", "Vis", "vis_enable")
+ DebugMenu:AddToggle(categoryId, categoryId.."_visfreeze", "Freeze Vis", nil, function(on)
+ if on then
+ SendToConsole("vis_debug_show 1")
+ Player:Delay(function()
+ SendToConsole("vis_debug_lock 1")
+ end)
+ else
+ SendToConsole("vis_debug_show 0")
+ SendToConsole("vis_debug_lock 0")
+ end
+ end)
-DebugMenu:AddToggle(categoryId, "cubemapcolors", "Cubemap Colors", "r_cubemap_debug_colors")
+ DebugMenu:SendCategoryToPanel(DebugMenu:GetCategory(categoryId))
+ DebugMenu:SetCategoryIndex(categoryId, 2)
-DebugMenu:AddToggle(categoryId, categoryId.."_visfreeze", "Freeze Vis", nil, function(on)
- if on then
- SendToConsole("vis_debug_show 1")
- Player:Delay(function()
- SendToConsole("vis_debug_lock 1")
- end)
- else
- SendToConsole("vis_debug_show 0")
- SendToConsole("vis_debug_lock 0")
+end
+
+---
+---Removes the AlyxLib Extras tab from the debug menu.
+---
+local function removeTab()
+ if not DebugMenu:GetCategory(categoryId) then
+ return
end
-end)
-return version
\ No newline at end of file
+ DebugMenu:RemoveCategory(categoryId)
+ DebugMenu:Refresh()
+end
+
+return {
+ version = version,
+ createTab = createTab,
+ removeTab = removeTab
+}
\ No newline at end of file
From d9c530ec23303111506e51ef249e87cae9208635 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Fri, 21 Nov 2025 03:34:35 +1300
Subject: [PATCH 130/146] docs: improve clarity
---
scripts/vscripts/alyxlib/debug/commands.lua | 3 +++
scripts/vscripts/alyxlib/helpers/easyconvars.lua | 2 +-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/scripts/vscripts/alyxlib/debug/commands.lua b/scripts/vscripts/alyxlib/debug/commands.lua
index 6668afa..1690fba 100644
--- a/scripts/vscripts/alyxlib/debug/commands.lua
+++ b/scripts/vscripts/alyxlib/debug/commands.lua
@@ -27,6 +27,9 @@ end
---
---Registers a new AlyxLib console variable.
---
+---If `defaultValue` or `callback` is a function, the convar will be
+---created using `EasyConvars`.
+---
---@param name string # Name of the convar that will be given in the console
---@param defaultValue string|function # Default value of the convar or initializer function
---@param helpText? string # Description of the convar
diff --git a/scripts/vscripts/alyxlib/helpers/easyconvars.lua b/scripts/vscripts/alyxlib/helpers/easyconvars.lua
index 6db727e..be37a81 100644
--- a/scripts/vscripts/alyxlib/helpers/easyconvars.lua
+++ b/scripts/vscripts/alyxlib/helpers/easyconvars.lua
@@ -488,7 +488,7 @@ function EasyConvars:SetRaw(name, value)
end
---
----Get if the convar was changed by the user in the console.
+---Gets if the convar was changed by the user in the console or via CFG.
---
---@param name string # Name of the convar
function EasyConvars:WasChangedByUser(name)
From 1ba3c54fe45a9702c1bca5ee02fd3515b3ad0340 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Fri, 21 Nov 2025 03:35:55 +1300
Subject: [PATCH 131/146] refactor: allow nil event id
Stops warning appearing when variable is initialized as nil
---
scripts/vscripts/alyxlib/player/events.lua | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/scripts/vscripts/alyxlib/player/events.lua b/scripts/vscripts/alyxlib/player/events.lua
index 3ffde77..d0f7290 100644
--- a/scripts/vscripts/alyxlib/player/events.lua
+++ b/scripts/vscripts/alyxlib/player/events.lua
@@ -62,8 +62,9 @@ end
---
---Unregisters a player event callback.
---
----@param eventID integer # ID returned from [ListenToPlayerEvent](lua://ListenToPlayerEvent)
+---@param eventID integer|nil # ID returned from [ListenToPlayerEvent](lua://ListenToPlayerEvent)
function StopListeningToPlayerEvent(eventID)
+ if not eventID then return end
for _, event in pairs(registered_event_callbacks) do
event[eventID] = nil
end
From c515a3894154759973b32d905538c9fcf243892f Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Fri, 21 Nov 2025 03:41:04 +1300
Subject: [PATCH 132/146] refactor: increase tab opacity to improve clarity
---
panorama/styles/custom_game/alyxlib_debug_menu.css | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/panorama/styles/custom_game/alyxlib_debug_menu.css b/panorama/styles/custom_game/alyxlib_debug_menu.css
index 4008de0..9edfc58 100644
--- a/panorama/styles/custom_game/alyxlib_debug_menu.css
+++ b/panorama/styles/custom_game/alyxlib_debug_menu.css
@@ -157,7 +157,7 @@ Button.custom_button_with_subtitle.error_msg:not(.disabled) Label.button_label.s
/* background-color: yellow; */
width: fill-parent-flow(1);
height: 100%;
- opacity: 0.5;
+ opacity: 1.0;
}
.CategoryButton
{
From 7ef046f412ba52484c828debe267c408709008a0 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Fri, 13 Mar 2026 21:05:56 +1300
Subject: [PATCH 133/146] refactor: update IsAddonEnabled to return boolean
value
---
scripts/vscripts/alyxlib/globals.lua | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/scripts/vscripts/alyxlib/globals.lua b/scripts/vscripts/alyxlib/globals.lua
index b213f9d..3de61fe 100644
--- a/scripts/vscripts/alyxlib/globals.lua
+++ b/scripts/vscripts/alyxlib/globals.lua
@@ -154,7 +154,7 @@ end
---@param workshopID string # The workshop ID of the addon
---@return boolean # `true` if the addon is enabled, `false` otherwise
function IsAddonEnabled(workshopID)
- return vlua.find(GetEnabledAddons(), workshopID)
+ return vlua.find(GetEnabledAddons(), workshopID) ~= nil
end
---
From 6515f2e4abd1896799719ff223e2955a9356792d Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Fri, 13 Mar 2026 21:10:23 +1300
Subject: [PATCH 134/146] fix: healme command setting health instead of adding
---
scripts/vscripts/alyxlib/debug/commands.lua | 9 ++-------
1 file changed, 2 insertions(+), 7 deletions(-)
diff --git a/scripts/vscripts/alyxlib/debug/commands.lua b/scripts/vscripts/alyxlib/debug/commands.lua
index 1690fba..6afdaf5 100644
--- a/scripts/vscripts/alyxlib/debug/commands.lua
+++ b/scripts/vscripts/alyxlib/debug/commands.lua
@@ -488,14 +488,9 @@ end, "Prints context criteria for an entity except for values saved using storag
---
RegisterAlyxLibCommand("healme", function (_, amount)
- amount = tonumber(amount)
+ amount = tonumber(amount) or 10
- if not amount then
- warn("Must provide a health amount")
- return
- end
-
- Player:SetHealth(amount)
+ Player:SetHealth(Player:GetHealth() + amount)
end, "Heals the player by a given amount", 0)
---
From dd52b0f181fa5b2063ed0dd31b9ef7901d79166b Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Fri, 13 Mar 2026 21:10:39 +1300
Subject: [PATCH 135/146] refactor: update RegisterAlyxLibConvar to enhance
command help text
---
scripts/vscripts/alyxlib/debug/commands.lua | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/scripts/vscripts/alyxlib/debug/commands.lua b/scripts/vscripts/alyxlib/debug/commands.lua
index 6afdaf5..0be39fb 100644
--- a/scripts/vscripts/alyxlib/debug/commands.lua
+++ b/scripts/vscripts/alyxlib/debug/commands.lua
@@ -43,7 +43,12 @@ function RegisterAlyxLibConvar(name, defaultValue, helpText, flags, callback)
else
Convars:RegisterConvar(name, defaultValue, helpText, flags)
end
- alyxlibCommands[name] = "Default: "..tostring(defaultValue)..", "..helpText
+ alyxlibCommands[name] = helpText
+
+ ---@TODO Support initializer values
+ if type(defaultValue) ~= "function" then
+ alyxlibCommands[name] = alyxlibCommands[name] .. ", default: "..tostring(defaultValue)
+ end
end
---
From 3adb98173273bf5c14655e2847e7f1a6881eae93 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Fri, 13 Mar 2026 21:57:45 +1300
Subject: [PATCH 136/146] refactor: extract alyxlib menus into own scripts
Moving towards making the debug menu a self contained feature, decoupled
from AlyxLib. This also brings a cheats menu.
---
scripts/vscripts/alyxlib/debug/debug_menu.lua | 83 -----------------
.../debug/menus/debug_menu_alyxlib.lua | 88 +++++++++++++++++++
.../alyxlib/debug/menus/debug_menu_cheats.lua | 72 +++++++++++++++
.../debug/{ => menus}/debug_menu_extras.lua | 8 +-
4 files changed, 165 insertions(+), 86 deletions(-)
create mode 100644 scripts/vscripts/alyxlib/debug/menus/debug_menu_alyxlib.lua
create mode 100644 scripts/vscripts/alyxlib/debug/menus/debug_menu_cheats.lua
rename scripts/vscripts/alyxlib/debug/{ => menus}/debug_menu_extras.lua (88%)
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index 67d10ec..cff5dbd 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -44,16 +44,6 @@ end, "Menu will float in world instead of attached to hand", 0, updateAttachment
RegisterAlyxLibConvar("debug_menu_lock", "0", "Prevents the debug menu from being repositioned by the player", 0)
-local extras = require("alyxlib.debug.debug_menu_extras")
-
-RegisterAlyxLibConvar("debug_menu_extras", "0", "Enable the extras tab by default", 0, function()
- if Convars:GetBool("debug_menu_extras") then
- extras.createTab()
- else
- extras.removeTab()
- end
-end)
-
RegisterAlyxLibCommand("debug_menu_dump_convars", function()
local categories = DebugMenu:GetLinkedConvars()
for _, category in pairs(categories) do
@@ -1209,82 +1199,9 @@ DebugMenu:AddCategory("settings", "")
DebugMenu:AddToggle("settings", "menu_hand", "Menu on primary hand", "debug_menu_hand")
DebugMenu:AddToggle("settings", "menu_floating", "Menu floating", "debug_menu_floating")
DebugMenu:AddToggle("settings", "menu_lock", "Menu locked", "debug_menu_lock")
-DebugMenu:AddToggle("settings", "menu_extras", "Menu extras", "debug_menu_extras")
DebugMenu:AddSlider("settings", "menu_width", "Menu width", "debug_menu_width", 10, 30, false, 0)
DebugMenu:AddSlider("settings", "menu_height", "Menu height", "debug_menu_height", 7, 30, false, 0)
DebugMenu:AddLabel("settings", "", "Print all current menu settings to the console for saving")
DebugMenu:AddButton("settings", "print_settings", "Print Settings To Console", "debug_menu_generate_cfg")
---[[
- Default AlyxLib tab
-]]
----@TODO Move to its own file
-
-local categoryId = "alyxlib"
-
-DebugMenu:AddCategory(categoryId, "AlyxLib")
-
-DebugMenu:AddSeparator(categoryId, nil, "Basic")
-
-if IsVREnabled() then
- DebugMenu:AddToggle(categoryId, "noclip_vr", "NoClip VR", "noclip_vr", nil, function()
- return Convars:GetBool("noclip_vr_enabled")
- end)
- DebugMenu:AddLabel(categoryId, "noclip_vr_label", "Hold movement trigger to boost")
- DebugMenu:AddSlider(categoryId, "noclip_vr_speed", "NoClip VR Speed", "noclip_vr_speed", 0.5, 10, false, 2)
- DebugMenu:AddSlider(categoryId, "noclip_vr_boost_speed", "NoClip VR Boost Speed", "noclip_vr_boost_speed", 0.5, 10, false, 2)
-end
-
-DebugMenu:AddToggle(categoryId, "buddha", "Buddha Mode", "buddha")
-
-DebugMenu:AddToggle(categoryId, "lefthanded", "Left Handed", "hlvr_left_hand_primary")
-
-DebugMenu:AddToggle(categoryId, "gameinstructor", "Game Instructor Hints", nil,
-function(on)
- Convars:SetBool("gameinstructor_enable", on)
- Convars:SetBool("sv_gameinstructor_disable", not on)
-end,
-function()
- return Convars:GetBool("gameinstructor_enable") and not Convars:GetBool("sv_gameinstructor_disable")
-end)
-
-DebugMenu:AddSeparator(categoryId, nil, "Equipment")
-
-if IsVREnabled() or IsFakeVREnabled() then
- DebugMenu:AddButton(categoryId, "givegrabbity", "Give Grabbity Gloves", "hlvr_give_grabbity_gloves")
-end
-
-DebugMenu:AddButton(categoryId, "giveammo", "Give 999 Ammo", function()
- SendToConsole("hlvr_setresources 999 999 999 " .. Player:GetResin())
-end)
-
-DebugMenu:AddButton(categoryId, "giveresin", "Give 999 Resin", function()
- SendToConsole("hlvr_addresources 0 0 0 " .. (999 - Player:GetResin()))
-end)
-
-DebugMenu:AddSeparator(categoryId, nil, "Session")
-
-local isRecordingDemo = false
-local currentDemo = ""
-
-DebugMenu:AddLabel(categoryId, "demo_recording_label", "Last Demo: N/A")
-
-DebugMenu:AddButton(categoryId, "demo_recording", "Start Recording Demo", function()
- if isRecordingDemo then
- SendToConsole("stop")
- currentDemo = ""
- isRecordingDemo = false
- DebugMenu:SetItemText(categoryId, "demo_recording", "Start Recording Demo")
- else
- local localtime = LocalTime()
- -- remove all whitespace and slashes
- local sanitizedMap = GetMapName():gsub("%s+", ""):gsub("/", "_")
- currentDemo = "demo_" .. sanitizedMap .. "_" .. localtime.Hours .. "-" .. localtime.Minutes .. "-" .. localtime.Seconds
- SendToConsole("record " .. currentDemo)
- DebugMenu:SetItemText(categoryId, "demo_recording_label", "Last Demo: " .. currentDemo .. ".dem")
- isRecordingDemo = true
- DebugMenu:SetItemText(categoryId, "demo_recording", "Stop Recording Demo")
- end
-end)
-
return DebugMenu.version
\ No newline at end of file
diff --git a/scripts/vscripts/alyxlib/debug/menus/debug_menu_alyxlib.lua b/scripts/vscripts/alyxlib/debug/menus/debug_menu_alyxlib.lua
new file mode 100644
index 0000000..713d521
--- /dev/null
+++ b/scripts/vscripts/alyxlib/debug/menus/debug_menu_alyxlib.lua
@@ -0,0 +1,88 @@
+--[[
+ v1.0.0
+ https://github.com/FrostSource/alyxlib
+
+ Default AlyxLib tab.
+]]
+
+local version = "v1.0.0"
+
+local cheats = require("alyxlib.debug.menus.debug_menu_cheats")
+
+RegisterAlyxLibConvar("debug_menu_cheats", "0", "Enables the cheats tab", 0, function()
+ if Convars:GetBool("debug_menu_cheats") then
+ cheats.createTab()
+ else
+ cheats.removeTab()
+ end
+end)
+
+local extras = require("alyxlib.debug.menus.debug_menu_extras")
+
+RegisterAlyxLibConvar("debug_menu_extras", "0", "Enables the extras tab", 0, function()
+ if Convars:GetBool("debug_menu_extras") then
+ extras.createTab()
+ else
+ extras.removeTab()
+ end
+end)
+
+-- Add extras and cheats to settings page
+if DebugMenu:GetCategory("settings") then
+ DebugMenu:AddToggle("settings", "menu_cheats", "Cheats tab", "debug_menu_cheats")
+ DebugMenu:AddToggle("settings", "menu_extras", "Developer tab", "debug_menu_extras")
+else
+ warn("Cannot add extras and cheats to settings tab: Settings tab does not exist!")
+end
+
+
+local categoryId = "alyxlib"
+
+DebugMenu:AddCategory(categoryId, "AlyxLib")
+
+DebugMenu:AddSeparator(categoryId, nil, "Basic")
+
+if IsVREnabled() then
+ DebugMenu:AddToggle(categoryId, "noclip_vr", "NoClip VR", "noclip_vr", nil, function()
+ return Convars:GetBool("noclip_vr_enabled")
+ end)
+ DebugMenu:AddLabel(categoryId, "noclip_vr_label", "Hold movement trigger to boost")
+ DebugMenu:AddSlider(categoryId, "noclip_vr_speed", "NoClip VR Speed", "noclip_vr_speed", 0.5, 10, false, 2)
+ DebugMenu:AddSlider(categoryId, "noclip_vr_boost_speed", "NoClip VR Boost Speed", "noclip_vr_boost_speed", 0.5, 10, false, 2)
+end
+
+DebugMenu:AddToggle(categoryId, "gameinstructor", "Game Instructor Hints", nil,
+function(on)
+ Convars:SetBool("gameinstructor_enable", on)
+ Convars:SetBool("sv_gameinstructor_disable", not on)
+end,
+function()
+ return Convars:GetBool("gameinstructor_enable") and not Convars:GetBool("sv_gameinstructor_disable")
+end)
+
+DebugMenu:AddSeparator(categoryId, nil, "Session")
+
+local isRecordingDemo = false
+local currentDemo = ""
+
+DebugMenu:AddLabel(categoryId, "demo_recording_label", "Last Demo: N/A")
+
+DebugMenu:AddButton(categoryId, "demo_recording", "Start Recording Demo", function()
+ if isRecordingDemo then
+ SendToConsole("stop")
+ currentDemo = ""
+ isRecordingDemo = false
+ DebugMenu:SetItemText(categoryId, "demo_recording", "Start Recording Demo")
+ else
+ local localtime = LocalTime()
+ -- remove all whitespace and slashes
+ local sanitizedMap = GetMapName():gsub("%s+", ""):gsub("/", "_")
+ currentDemo = "demo_" .. sanitizedMap .. "_" .. localtime.Hours .. "-" .. localtime.Minutes .. "-" .. localtime.Seconds
+ SendToConsole("record " .. currentDemo)
+ DebugMenu:SetItemText(categoryId, "demo_recording_label", "Last Demo: " .. currentDemo .. ".dem")
+ isRecordingDemo = true
+ DebugMenu:SetItemText(categoryId, "demo_recording", "Stop Recording Demo")
+ end
+end)
+
+return version
\ No newline at end of file
diff --git a/scripts/vscripts/alyxlib/debug/menus/debug_menu_cheats.lua b/scripts/vscripts/alyxlib/debug/menus/debug_menu_cheats.lua
new file mode 100644
index 0000000..95390d2
--- /dev/null
+++ b/scripts/vscripts/alyxlib/debug/menus/debug_menu_cheats.lua
@@ -0,0 +1,72 @@
+--[[
+ v1.0.0
+ https://github.com/FrostSource/alyxlib
+
+ Cheats debug menu category for players.
+]]
+
+local version = "v1.0.0"
+
+-- Simple require can cause loop with debug_menu.lua
+if package.loaded["alyxlib.debug.debug_menu"] == nil then
+ require "alyxlib.debug.debug_menu"
+end
+
+local categoryId = "alyxlib_cheats"
+
+---
+---Creates the AlyxLib Extras tab in the debug menu.
+---
+local function createTab()
+ if DebugMenu:GetCategory(categoryId) then
+ return
+ end
+
+ DebugMenu:AddCategory(categoryId, "Cheats")
+
+ DebugMenu:AddToggle(categoryId, "buddha", "Buddha Mode", "buddha")
+
+ DebugMenu:AddToggle(categoryId, "notarget", "No Target", "notarget")
+
+ DebugMenu:AddButton(categoryId, "heal", "Heal", function()
+ Entities:GetLocalPlayer():SetHealth(100)
+ end)
+
+ DebugMenu:AddSeparator(categoryId, nil, "Equipment")
+
+ if IsVREnabled() or IsFakeVREnabled() then
+ DebugMenu:AddButton(categoryId, "givegrabbity", "Give Grabbity Gloves", "hlvr_give_grabbity_gloves")
+ end
+
+ DebugMenu:AddToggle(categoryId, "infiniteammo", "Infinite Ammo", "sv_infinite_ammo")
+
+ DebugMenu:AddButton(categoryId, "giveammo", "Give 999 Ammo", function()
+ SendToConsole("hlvr_setresources 999 999 999 " .. Player:GetResin())
+ end)
+
+ DebugMenu:AddButton(categoryId, "giveresin", "Give 999 Resin", function()
+ SendToConsole("hlvr_addresources 0 0 0 " .. (999 - Player:GetResin()))
+ end)
+
+ DebugMenu:SendCategoryToPanel(DebugMenu:GetCategory(categoryId))
+ DebugMenu:SetCategoryIndex(categoryId, 2)
+
+end
+
+---
+---Removes the AlyxLib Extras tab from the debug menu.
+---
+local function removeTab()
+ if not DebugMenu:GetCategory(categoryId) then
+ return
+ end
+
+ DebugMenu:RemoveCategory(categoryId)
+ DebugMenu:Refresh()
+end
+
+return {
+ version = version,
+ createTab = createTab,
+ removeTab = removeTab
+}
\ No newline at end of file
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu_extras.lua b/scripts/vscripts/alyxlib/debug/menus/debug_menu_extras.lua
similarity index 88%
rename from scripts/vscripts/alyxlib/debug/debug_menu_extras.lua
rename to scripts/vscripts/alyxlib/debug/menus/debug_menu_extras.lua
index f92ab6d..5c1870a 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu_extras.lua
+++ b/scripts/vscripts/alyxlib/debug/menus/debug_menu_extras.lua
@@ -2,7 +2,7 @@
v1.0.0
https://github.com/FrostSource/alyxlib
- Extra debug menu category which can be enabled via the AlyxLib debug menu tab.
+ Extra debug menu category for developers.
]]
local version = "v1.0.0"
@@ -22,7 +22,7 @@ local function createTab()
return
end
- DebugMenu:AddCategory(categoryId, "AlyxLib Extras")
+ DebugMenu:AddCategory(categoryId, "Developer")
DebugMenu:AddSeparator(categoryId, nil, "Display")
@@ -49,8 +49,10 @@ local function createTab()
end
end)
+ DebugMenu:AddToggle(categoryId, "lefthanded", "Left Handed", "hlvr_left_hand_primary")
+
DebugMenu:SendCategoryToPanel(DebugMenu:GetCategory(categoryId))
- DebugMenu:SetCategoryIndex(categoryId, 2)
+ DebugMenu:SetCategoryIndex(categoryId, 3)
end
From 65b022004018d1bd1a8989a5357c50c9c3f5c88c Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Fri, 13 Mar 2026 22:03:28 +1300
Subject: [PATCH 137/146] feat: allow dynamic category count per menu width
---
.../scripts/custom_game/alyxlib_debug_menu.js | 29 ++++++++++++-------
.../styles/custom_game/alyxlib_debug_menu.css | 2 +-
scripts/vscripts/alyxlib/debug/debug_menu.lua | 6 ++--
3 files changed, 23 insertions(+), 14 deletions(-)
diff --git a/panorama/scripts/custom_game/alyxlib_debug_menu.js b/panorama/scripts/custom_game/alyxlib_debug_menu.js
index da206f5..466c0db 100644
--- a/panorama/scripts/custom_game/alyxlib_debug_menu.js
+++ b/panorama/scripts/custom_game/alyxlib_debug_menu.js
@@ -5,6 +5,7 @@
let panelReady = false;
+/** @type {Category} */
let settingsCategory = null;
/**
@@ -31,7 +32,7 @@ function FireOutput(outputName, ...args) {
*/
let categories = [];
-const numberOfVisibleCategories = 5;
+let numberOfVisibleCategories = 5;
let categoryBarCycleIndex = 0;
@@ -874,6 +875,10 @@ function CycleCategories(direction)
*/
function UpdateCategoryBarVisibility()
{
+ const bgPanel = $("#background_panel");
+ const width = bgPanel.actuallayoutwidth;
+ numberOfVisibleCategories = Math.max(1, Math.floor(width / 190));
+
const selectedIndex = categories.indexOf(currentlySelectedCategory);
if (selectedIndex < categoryBarCycleIndex) {
categoryBarCycleIndex = selectedIndex;
@@ -1056,13 +1061,15 @@ function ClickHoveredButton()
*/
function SetContainerSize(width, height) {
// Clear container clip so larger sizes don't get clipped before the schedule
- var container = $("#affordance_container");
+ const container = $("#affordance_container");
container.style.clip = "rect(0px, 100%, 100%, 0px)";
- let bgPanel = $("#background_panel");
+ const bgPanel = $("#background_panel");
- if (width)
+ if (width) {
bgPanel.style.width = `${width}px`;
+ $.Schedule(0.1, ()=> { UpdateCategoryBarVisibility(); });
+ }
if (height) {
bgPanel.style.height = `${height}px`;
$('#CategoriesContainer').style.minHeight = `${height - 111}px`;
@@ -1071,17 +1078,17 @@ function SetContainerSize(width, height) {
// Wait for element update and any animations
// 0.6s is the duration of the opening animation
$.Schedule(0.61, ()=> {
- var bgWidth = bgPanel.actuallayoutwidth;
- var bgHeight = bgPanel.actuallayoutheight;
- var containerWidth = container.actuallayoutwidth;
- var containerHeight = container.actuallayoutheight;
+ let bgWidth = bgPanel.actuallayoutwidth;
+ let bgHeight = bgPanel.actuallayoutheight;
+ let containerWidth = container.actuallayoutwidth;
+ let containerHeight = container.actuallayoutheight;
// Calculate centered x position within container
- var x = (containerWidth - bgWidth) / 2;
+ let x = (containerWidth - bgWidth) / 2;
// Calculate the bottom y position
- var y = (containerHeight - bgHeight);
+ let y = (containerHeight - bgHeight);
- var clipRect = "rect(" + y + "px, " + (x + bgWidth) + "px, " + (y + bgHeight) + "px, " + x + "px)";
+ const clipRect = "rect(" + y + "px, " + (x + bgWidth) + "px, " + (y + bgHeight) + "px, " + x + "px)";
container.style.clip = clipRect;
});
diff --git a/panorama/styles/custom_game/alyxlib_debug_menu.css b/panorama/styles/custom_game/alyxlib_debug_menu.css
index 9edfc58..5ddfdc7 100644
--- a/panorama/styles/custom_game/alyxlib_debug_menu.css
+++ b/panorama/styles/custom_game/alyxlib_debug_menu.css
@@ -161,7 +161,7 @@ Button.custom_button_with_subtitle.error_msg:not(.disabled) Label.button_label.s
}
.CategoryButton
{
- width: 150px;
+ width: fill-parent-flow(1);
height: 100%;
/* background-color: red; */
horizontal-align: left;
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index cff5dbd..87971d4 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -895,13 +895,15 @@ function DebugMenu:SetCategoryIndex(categoryId, index)
return
end
- index = math.max(1, math.min(index, #self.categories)) -- Clamp index to valid range
+ -- Account for 'settings' as invisible first tab
+ index = index + 1
+ index = Clamp(index, 2, #self.categories)
table.remove(self.categories, currentIndex)
table.insert(self.categories, index, category)
if self.panel then
- Panorama:Send(self.panel, "SetCategoryIndex", categoryId, index-1)
+ Panorama:Send(self.panel, "SetCategoryIndex", categoryId, index - 2)
end
end
From 1cb4851b8be17d938880da7f854229f93732a5b1 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Fri, 13 Mar 2026 22:04:07 +1300
Subject: [PATCH 138/146] feat: decode Lua encoded strings
---
panorama/scripts/custom_game/panorama_lua.js | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/panorama/scripts/custom_game/panorama_lua.js b/panorama/scripts/custom_game/panorama_lua.js
index d9925a5..5b842f6 100644
--- a/panorama/scripts/custom_game/panorama_lua.js
+++ b/panorama/scripts/custom_game/panorama_lua.js
@@ -17,6 +17,13 @@
// @ts-ignore
if(false)p=require("./panoramadoc");
+function DecodeString(str)
+{
+ str = str.replace(/U\+000A/g, "\n");
+ str = str.replace(/U\+00027/g, "'");
+ return str;
+}
+
/**
*
* @param {null} _ Unused variable.
@@ -25,7 +32,7 @@ if(false)p=require("./panoramadoc");
function LuaCallback(_, encoded)
{
// $.Msg(encoded)
- let decoded = encoded.split("|");
+ let decoded = DecodeString(encoded).split("|");
let panel = $.GetContextPanel();
//$.Msg(decoded[0], panel.BHasClass(decoded[0]))
From 79ba0d7a29c01413341df7e94732feff2d3b2f98 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Fri, 13 Mar 2026 22:04:41 +1300
Subject: [PATCH 139/146] fix: improve floating height for debug menu
---
scripts/vscripts/alyxlib/debug/debug_menu.lua | 1 +
1 file changed, 1 insertion(+)
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index 87971d4..e67f87f 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -462,6 +462,7 @@ function DebugMenu:UpdateMenuAttachment()
if Convars:GetBool("debug_menu_floating") or not Player.HMDAvatar or IsFakeVREnabled() then
local player = Entities:GetLocalPlayer()
local eyePos = player:EyePosition()
+ eyePos = eyePos + player:EyeAngles():Up() * -((Convars:GetFloat("debug_menu_height") or 0) / 2)
local fDir = player:EyeAngles():Forward()
local fAng = VectorToAngles(fDir)
fAng = RotateOrientation(fAng, QAngle(0, -90, 90))
From 8370a9cac4fd74124d288a6257951355d613ec7a Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Fri, 13 Mar 2026 22:08:27 +1300
Subject: [PATCH 140/146] fix: spawn menu at max size to allow instant resizing
---
scripts/vscripts/alyxlib/debug/debug_menu.lua | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index e67f87f..b085a4b 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -500,8 +500,8 @@ function DebugMenu:ShowMenu()
self.panel = SpawnEntityFromTableSynchronous("point_clientui_world_panel", {
targetname = "alyxlib_debug_menu",
dialog_layout_name = "file://{resources}/layout/custom_game/alyxlib_debug_menu.xml",
- width = 30,
- height = 30,
+ width = MAX_WIDTH,
+ height = MAX_HEIGHT,
panel_dpi = DPI,
ignore_input = 0,
lit = 0,
From 4e009b3fa3be67fbc8ca3d1f0300a8f083d431b9 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Fri, 13 Mar 2026 22:09:20 +1300
Subject: [PATCH 141/146] feat: add debug menu pop up dialog
---
.../layout/custom_game/alyxlib_debug_menu.xml | 141 ++++++++++++------
.../scripts/custom_game/alyxlib_debug_menu.js | 24 +++
.../styles/custom_game/alyxlib_debug_menu.css | 63 +++++++-
scripts/vscripts/alyxlib/debug/debug_menu.lua | 10 ++
4 files changed, 188 insertions(+), 50 deletions(-)
diff --git a/panorama/layout/custom_game/alyxlib_debug_menu.xml b/panorama/layout/custom_game/alyxlib_debug_menu.xml
index ed9604c..3d68935 100644
--- a/panorama/layout/custom_game/alyxlib_debug_menu.xml
+++ b/panorama/layout/custom_game/alyxlib_debug_menu.xml
@@ -19,69 +19,112 @@
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
diff --git a/panorama/scripts/custom_game/alyxlib_debug_menu.js b/panorama/scripts/custom_game/alyxlib_debug_menu.js
index 466c0db..9d2c94b 100644
--- a/panorama/scripts/custom_game/alyxlib_debug_menu.js
+++ b/panorama/scripts/custom_game/alyxlib_debug_menu.js
@@ -971,6 +971,24 @@ function ShowSettings()
}
}
+function OpenDialog(text)
+{
+ const dialog = $('#modal_background');
+ const label = $('#dialog_label');
+ const contentContainer = $('#ContentContainer');
+ label.text = text;
+ dialog.visible = true;
+ contentContainer.style.blur = "gaussian( 5 )";
+}
+
+function CloseDialog()
+{
+ const dialog = $('#modal_background');
+ const contentContainer = $('#ContentContainer');
+ dialog.visible = false;
+ contentContainer.style.blur = "none";
+}
+
/**
* Gets the position of the left or right 'affordance' circle for the VR finger interacting with the menu.
* @returns {{x:number,y:number}?}
@@ -1267,6 +1285,12 @@ function ParseCommand(command, args)
SetContainerSize(width, height);
break;
}
+
+ case "showdialog": {
+ const text = args[0];
+ OpenDialog(text);
+ break;
+ }
}
}
diff --git a/panorama/styles/custom_game/alyxlib_debug_menu.css b/panorama/styles/custom_game/alyxlib_debug_menu.css
index 5ddfdc7..2ec5863 100644
--- a/panorama/styles/custom_game/alyxlib_debug_menu.css
+++ b/panorama/styles/custom_game/alyxlib_debug_menu.css
@@ -1,7 +1,8 @@
#background_panel.bg
{
+ /* background_panel size is updated in JS SetContainerSize() */
/*opacity-mask: url("s2r://panorama/images/game_menu_ui/menu_type_mask_psd.vtex");*/
- flow-children: down;
+ flow-children: none;
width: 100%;
background-color: gradient( radial, 50% 50%, 0% 0%, 100% 100%, from( #553c18 ), color-stop( 0.97, #000000 ), to( #000000 ) );
box-shadow: inset #995e0011 2px 2px 4px 4px;
@@ -10,6 +11,10 @@
horizontal-align: center;
vertical-align: bottom;
}
+#ContentContainer
+{
+ blur: none;
+}
.vr_affordance_container{
width: 100%;
height: 100%;
@@ -439,4 +444,60 @@ Label.custom_label
{
background-image: url("s2r://panorama/images/game_menu_ui/options_advanced_hover_png.vtex");
background-color: #FFBE5588;
+}
+
+
+
+#modal_dialog_container
+{
+ width: 100%;
+ height: 100%;
+ flow-children: none;
+}
+#modal_dialog
+{
+ horizontal-align: center;
+ vertical-align: middle;
+ /* background-color: red; */
+ /* z: 100px; */
+ /* perspective: 100; */
+ background-color: gradient( radial, 50% 50%, 0% 0%, 100% 100%, from( #553c18ee ), color-stop( 0.97, #000000 ), to( #000000 ) );
+ /* box-shadow: inset #995e0011 2px 2px 4px 4px; */
+ opacity: 0.99;
+ border-radius: 1%;
+}
+#modal_background
+{
+ width: 100%;
+ height: 100%;
+ background-color: #00000055;
+ visibility: collapse;
+}
+
+.centered
+{
+ horizontal-align: center;
+ vertical-align: middle;
+}
+
+.button_modal
+{
+ width: 220px;
+ min-height: 78px;
+ /* margin-top: 24px;
+ margin-right: 24px;
+ padding-top: 8px;
+ background-color: #FFBE5533;
+ border: 2px solid #FFBE55;
+ text-align: center; */
+}
+
+#dialog_label
+{
+ font-size: 38px;
+ text-align: center;
+ margin: 24px;
+ /* Bottom has a gap for unknown reasons at certain font sizes */
+ /* This doesn't fix it but lessens it */
+ margin-bottom: 0px;
}
\ No newline at end of file
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index b085a4b..7f47a96 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -856,6 +856,16 @@ function DebugMenu:AddCycle(categoryId, cycleId, title, convar, values, callback
})
end
+---
+---Shows a dialog box with the specified text and a "CLOSE" button.
+---
+---@param text string # The text to display in the dialog
+function DebugMenu:ShowDialog(text)
+ if self.panel then
+ Panorama:Send(self.panel, "ShowDialog", text)
+ end
+end
+
---
---Sets the text of an item.
---
From 39d7338a2279a5ef08f8626acc281ec28506ddc8 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Fri, 13 Mar 2026 22:11:30 +1300
Subject: [PATCH 142/146] fix: warn users when dataString exceeds 404 chars
Game clamps or fails above certain lengths (404 and 462). Will need to
implement queued SendToConsole.
---
scripts/vscripts/alyxlib/panorama/core.lua | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/scripts/vscripts/alyxlib/panorama/core.lua b/scripts/vscripts/alyxlib/panorama/core.lua
index 2acc47f..90886b8 100644
--- a/scripts/vscripts/alyxlib/panorama/core.lua
+++ b/scripts/vscripts/alyxlib/panorama/core.lua
@@ -99,6 +99,13 @@ function Panorama:Send(panelEntity, ...)
dataString = FilterText(dataString)
+ -- above 404 it will be clamped
+ -- above 462 it will be completely ignored
+ if #dataString > 404 then
+ warn("Panorama string length", #dataString, "exceeds 404 characters and may be truncated! Consider reducing the amount of data being sent or splitting it into multiple sends.")
+ ---@TODO split into multiple sends, need a way to buffer SendToConsole calls and ensure they are sent in order
+ end
+
-- print("Sending to pano:", dataString)
SendToConsole("@panorama_dispatch_event AddStyleToEachChild('"..dataString.."')")
end
From a8623115444e2e039283aa90a7bd57ec21197dd0 Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Fri, 13 Mar 2026 23:35:56 +1300
Subject: [PATCH 143/146] chore: update versions
---
scripts/vscripts/alyxlib/class.lua | 4 ++--
scripts/vscripts/alyxlib/controls/gesture.lua | 4 ++--
scripts/vscripts/alyxlib/controls/haptics.lua | 4 ++--
scripts/vscripts/alyxlib/controls/input.lua | 4 ++--
scripts/vscripts/alyxlib/data/color.lua | 4 ++--
scripts/vscripts/alyxlib/data/inventory.lua | 4 ++--
scripts/vscripts/alyxlib/data/queue.lua | 4 ++--
scripts/vscripts/alyxlib/data/stack.lua | 4 ++--
scripts/vscripts/alyxlib/debug/commands.lua | 4 ++--
scripts/vscripts/alyxlib/debug/common.lua | 4 ++--
scripts/vscripts/alyxlib/debug/controller.lua | 4 ++--
scripts/vscripts/alyxlib/debug/debug_menu.lua | 4 ++--
scripts/vscripts/alyxlib/debug/novr.lua | 4 ++--
scripts/vscripts/alyxlib/debug/profiling.lua | 2 +-
scripts/vscripts/alyxlib/debug/vr.lua | 4 ++--
scripts/vscripts/alyxlib/extensions/entities.lua | 4 ++--
scripts/vscripts/alyxlib/extensions/entity.lua | 4 ++--
scripts/vscripts/alyxlib/extensions/npc.lua | 4 ++--
scripts/vscripts/alyxlib/extensions/qangle.lua | 4 ++--
scripts/vscripts/alyxlib/extensions/string.lua | 4 ++--
scripts/vscripts/alyxlib/extensions/template.lua | 4 ++--
scripts/vscripts/alyxlib/extensions/vector.lua | 4 ++--
scripts/vscripts/alyxlib/globals.lua | 4 ++--
scripts/vscripts/alyxlib/helpers/animation.lua | 4 ++--
scripts/vscripts/alyxlib/helpers/easyconvars.lua | 4 ++--
scripts/vscripts/alyxlib/init.lua | 7 ++++---
scripts/vscripts/alyxlib/math/common.lua | 4 ++--
scripts/vscripts/alyxlib/math/weighted_random.lua | 4 ++--
scripts/vscripts/alyxlib/panorama/core.lua | 4 ++--
scripts/vscripts/alyxlib/player/core.lua | 4 ++--
scripts/vscripts/alyxlib/player/events.lua | 4 ++--
scripts/vscripts/alyxlib/player/hands.lua | 4 ++--
scripts/vscripts/alyxlib/player/wrist_attachments.lua | 4 ++--
scripts/vscripts/alyxlib/precache.lua | 4 ++--
scripts/vscripts/alyxlib/storage.lua | 4 ++--
scripts/vscripts/alyxlib/utils/common.lua | 4 ++--
scripts/vscripts/alyxlib/utils/enums.lua | 2 +-
scripts/vscripts/alyxlib/utils/extension_duplicate_fix.lua | 2 +-
version.json | 2 +-
39 files changed, 76 insertions(+), 75 deletions(-)
diff --git a/scripts/vscripts/alyxlib/class.lua b/scripts/vscripts/alyxlib/class.lua
index 23dd473..0789031 100644
--- a/scripts/vscripts/alyxlib/class.lua
+++ b/scripts/vscripts/alyxlib/class.lua
@@ -1,5 +1,5 @@
--[[
- v2.3.1
+ v2.4.0
https://github.com/FrostSource/alyxlib
Provides object-oriented class and inheritance functionality.
@@ -8,7 +8,7 @@
require "alyxlib.class"
]]
-local version = "v2.3.1"
+local version = "v2.4.0"
require "alyxlib.storage"
require "alyxlib.globals"
diff --git a/scripts/vscripts/alyxlib/controls/gesture.lua b/scripts/vscripts/alyxlib/controls/gesture.lua
index 651a5e9..ca11df0 100644
--- a/scripts/vscripts/alyxlib/controls/gesture.lua
+++ b/scripts/vscripts/alyxlib/controls/gesture.lua
@@ -1,5 +1,5 @@
--[[
- v1.2.0
+ v1.2.1
https://github.com/FrostSource/alyxlib
Provides a system for tracking simple hand poses and gestures.
@@ -48,7 +48,7 @@ Gesture.__index = Gesture
---
---Current version of the gesture system.
---
-Gesture.version = "v1.2.0"
+Gesture.version = "v1.2.1"
---
---If the gesture system should start automatically on player spawn.
diff --git a/scripts/vscripts/alyxlib/controls/haptics.lua b/scripts/vscripts/alyxlib/controls/haptics.lua
index 84e52fc..e0d8e3b 100644
--- a/scripts/vscripts/alyxlib/controls/haptics.lua
+++ b/scripts/vscripts/alyxlib/controls/haptics.lua
@@ -1,5 +1,5 @@
--[[
- v1.0.2
+ v1.0.3
https://github.com/FrostSource/alyxlib
Haptic sequences allow for more complex vibrations than the one-shot pulses that the base API provides.
@@ -35,7 +35,7 @@ HapticSequenceClass.__index = HapticSequenceClass
---
---Current version of the haptics system.
---
-HapticSequenceClass.version = "v1.0.2"
+HapticSequenceClass.version = "v1.0.3"
---
---Start the haptic sequence on a given hand.
diff --git a/scripts/vscripts/alyxlib/controls/input.lua b/scripts/vscripts/alyxlib/controls/input.lua
index a743f65..5ddb8ad 100644
--- a/scripts/vscripts/alyxlib/controls/input.lua
+++ b/scripts/vscripts/alyxlib/controls/input.lua
@@ -1,5 +1,5 @@
--[[
- v4.0.2
+ v4.1.0
https://github.com/FrostSource/alyxlib
Simplifies the tracking of digital action presses/releases and analog values.
@@ -15,7 +15,7 @@
---@class Input
Input = {}
Input.__index = Input
-Input.version = "v4.0.2"
+Input.version = "v4.1.0"
---
---If the input system should start automatically on player spawn.
diff --git a/scripts/vscripts/alyxlib/data/color.lua b/scripts/vscripts/alyxlib/data/color.lua
index 2a000c4..68fcad8 100644
--- a/scripts/vscripts/alyxlib/data/color.lua
+++ b/scripts/vscripts/alyxlib/data/color.lua
@@ -1,5 +1,5 @@
--[[
- v1.0.2
+ v1.0.3
https://github.com/FrostSource/alyxlib
If not using `alyxlib/init.lua`, load this file at game start using the following line:
@@ -9,7 +9,7 @@
require "alyxlib.globals"
require "alyxlib.math.common"
-local version = "v1.0.2"
+local version = "v1.0.3"
local hasfrac = math.has_frac
diff --git a/scripts/vscripts/alyxlib/data/inventory.lua b/scripts/vscripts/alyxlib/data/inventory.lua
index 2912cd5..0c5f8d1 100644
--- a/scripts/vscripts/alyxlib/data/inventory.lua
+++ b/scripts/vscripts/alyxlib/data/inventory.lua
@@ -1,5 +1,5 @@
--[[
- v1.2.3
+ v1.2.4
https://github.com/FrostSource/alyxlib
An inventory is a table where each key has an integer value assigned to it.
@@ -10,7 +10,7 @@
require "alyxlib.data.inventory"
]]
-local version = "v1.2.3"
+local version = "v1.2.4"
---
---Inventory data structure.
diff --git a/scripts/vscripts/alyxlib/data/queue.lua b/scripts/vscripts/alyxlib/data/queue.lua
index b198045..f6d4dda 100644
--- a/scripts/vscripts/alyxlib/data/queue.lua
+++ b/scripts/vscripts/alyxlib/data/queue.lua
@@ -1,5 +1,5 @@
--[[
- v1.5.1
+ v1.5.2
https://github.com/FrostSource/alyxlib
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.
@@ -9,7 +9,7 @@
require "alyxlib.data.queue"
]]
-local version = "v1.5.1"
+local version = "v1.5.2"
---
---Queue data structure.
diff --git a/scripts/vscripts/alyxlib/data/stack.lua b/scripts/vscripts/alyxlib/data/stack.lua
index f098fa7..b3afb56 100644
--- a/scripts/vscripts/alyxlib/data/stack.lua
+++ b/scripts/vscripts/alyxlib/data/stack.lua
@@ -1,5 +1,5 @@
--[[
- v1.2.4
+ v1.2.5
https://github.com/FrostSource/alyxlib
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.
@@ -9,7 +9,7 @@
require "alyxlib.data.stack"
]]
-local version = "v1.2.4"
+local version = "v1.2.5"
---
---Stack data structure.
diff --git a/scripts/vscripts/alyxlib/debug/commands.lua b/scripts/vscripts/alyxlib/debug/commands.lua
index 0be39fb..d176cad 100644
--- a/scripts/vscripts/alyxlib/debug/commands.lua
+++ b/scripts/vscripts/alyxlib/debug/commands.lua
@@ -1,5 +1,5 @@
--[[
- v1.1.1
+ v1.2.0
https://github.com/FrostSource/alyxlib
If not using `alyxlib/init.lua`, load this file at game start using the following line:
@@ -7,7 +7,7 @@
require "alyxlib.debug.commands"
]]
-local version = "v1.1.1"
+local version = "v1.2.0"
local alyxlibCommands = {}
diff --git a/scripts/vscripts/alyxlib/debug/common.lua b/scripts/vscripts/alyxlib/debug/common.lua
index 840d2b0..eece187 100644
--- a/scripts/vscripts/alyxlib/debug/common.lua
+++ b/scripts/vscripts/alyxlib/debug/common.lua
@@ -1,5 +1,5 @@
--[[
- v2.2.0
+ v2.3.0
https://github.com/FrostSource/alyxlib
Debug utility functions.
@@ -13,7 +13,7 @@ require "alyxlib.extensions.entity"
require "alyxlib.math.common"
Debug = {}
-Debug.version = "v2.2.0"
+Debug.version = "v2.3.0"
---
---Finds the first entity whose name, class or model matches `pattern`.
diff --git a/scripts/vscripts/alyxlib/debug/controller.lua b/scripts/vscripts/alyxlib/debug/controller.lua
index 4467dd3..c8aa816 100644
--- a/scripts/vscripts/alyxlib/debug/controller.lua
+++ b/scripts/vscripts/alyxlib/debug/controller.lua
@@ -1,5 +1,5 @@
--[[
- v1.0.2
+ v1.0.3
https://github.com/FrostSource/alyxlib
Allows quick debugging of VR controllers.
@@ -12,7 +12,7 @@
-- Used for button descriptions
require "alyxlib.controls.input"
-local version = "v1.0.2"
+local version = "v1.0.3"
Convars:RegisterCommand("alyxlib_start_print_controller_button_presses", function (_)
diff --git a/scripts/vscripts/alyxlib/debug/debug_menu.lua b/scripts/vscripts/alyxlib/debug/debug_menu.lua
index 7f47a96..5a013dc 100644
--- a/scripts/vscripts/alyxlib/debug/debug_menu.lua
+++ b/scripts/vscripts/alyxlib/debug/debug_menu.lua
@@ -1,5 +1,5 @@
--[[
- v1.0.0
+ v1.1.0
https://github.com/FrostSource/alyxlib
The debug menu allows for easier VR testing by offering a customizable in-game menu.
@@ -109,7 +109,7 @@ local handAttachment = ""
---
---@class DebugMenu
DebugMenu = {}
-DebugMenu.version = "v1.0.0"
+DebugMenu.version = "v1.1.0"
---
---A category of items in the debug menu.
diff --git a/scripts/vscripts/alyxlib/debug/novr.lua b/scripts/vscripts/alyxlib/debug/novr.lua
index abc1b9f..6bdcbc4 100644
--- a/scripts/vscripts/alyxlib/debug/novr.lua
+++ b/scripts/vscripts/alyxlib/debug/novr.lua
@@ -1,5 +1,5 @@
--[[
- v1.1.0
+ v1.2.0
https://github.com/FrostSource/alyxlib
Adds functions and console commands to help debugging outside VR mode.
@@ -9,7 +9,7 @@
require "alyxlib.debug.novr"
]]
-local version = "v1.1.0"
+local version = "v1.2.0"
---
---Provides functions to help debugging outside VR mode.
diff --git a/scripts/vscripts/alyxlib/debug/profiling.lua b/scripts/vscripts/alyxlib/debug/profiling.lua
index e2b1be7..bb10b17 100644
--- a/scripts/vscripts/alyxlib/debug/profiling.lua
+++ b/scripts/vscripts/alyxlib/debug/profiling.lua
@@ -1,5 +1,5 @@
--[[
- v1.0.0
+ v1.0.1
https://github.com/FrostSource/alyxlib
Profiler allows for easy profiling of functions.
diff --git a/scripts/vscripts/alyxlib/debug/vr.lua b/scripts/vscripts/alyxlib/debug/vr.lua
index acb33cb..e7a72ec 100644
--- a/scripts/vscripts/alyxlib/debug/vr.lua
+++ b/scripts/vscripts/alyxlib/debug/vr.lua
@@ -1,5 +1,5 @@
--[[
- v1.1.0
+ v1.2.0
https://github.com/FrostSource/alyxlib
Adds console commands to help debugging VR specific features.
@@ -9,7 +9,7 @@
require "alyxlib.debug.vr"
]]
-local version = "v1.1.0"
+local version = "v1.2.0"
---Get a hand entity from its name.
---@param handName string
diff --git a/scripts/vscripts/alyxlib/extensions/entities.lua b/scripts/vscripts/alyxlib/extensions/entities.lua
index 8375ece..79fbdc0 100644
--- a/scripts/vscripts/alyxlib/extensions/entities.lua
+++ b/scripts/vscripts/alyxlib/extensions/entities.lua
@@ -1,5 +1,5 @@
--[[
- v1.8.0
+ v1.8.1
https://github.com/FrostSource/alyxlib
Extensions for the `Entities` class.
@@ -10,7 +10,7 @@
]]
require "alyxlib.extensions.entity"
-local version = "v1.8.0"
+local version = "v1.8.1"
---
diff --git a/scripts/vscripts/alyxlib/extensions/entity.lua b/scripts/vscripts/alyxlib/extensions/entity.lua
index e04750a..21f587a 100644
--- a/scripts/vscripts/alyxlib/extensions/entity.lua
+++ b/scripts/vscripts/alyxlib/extensions/entity.lua
@@ -1,5 +1,5 @@
--[[
- v2.7.1
+ v2.8.0
https://github.com/FrostSource/alyxlib
Provides base entity extension methods.
@@ -9,7 +9,7 @@
require "alyxlib.extensions.entity"
]]
-local version = "v2.7.1"
+local version = "v2.8.0"
---
---Gets the entities parented to this entity. Including children of children.
diff --git a/scripts/vscripts/alyxlib/extensions/npc.lua b/scripts/vscripts/alyxlib/extensions/npc.lua
index 8e4fde1..d90682f 100644
--- a/scripts/vscripts/alyxlib/extensions/npc.lua
+++ b/scripts/vscripts/alyxlib/extensions/npc.lua
@@ -1,5 +1,5 @@
--[[
- v1.2.0
+ v1.2.1
https://github.com/FrostSource/alyxlib
Extension for NPC entities.
@@ -14,7 +14,7 @@ require "alyxlib.globals"
require "alyxlib.extensions.entity"
require "alyxlib.extensions.entities"
-local version = "v1.2.0"
+local version = "v1.2.1"
---
---Schedule enum table for use with [CAI_BaseNPC:StartSchedule](lua://CAI_BaseNPC.StartSchedule).
diff --git a/scripts/vscripts/alyxlib/extensions/qangle.lua b/scripts/vscripts/alyxlib/extensions/qangle.lua
index fd9e9f9..bae7099 100644
--- a/scripts/vscripts/alyxlib/extensions/qangle.lua
+++ b/scripts/vscripts/alyxlib/extensions/qangle.lua
@@ -1,5 +1,5 @@
--[[
- v1.0.0
+ v1.0.1
https://github.com/FrostSource/alyxlib
Provides QAngle class extension methods.
@@ -10,7 +10,7 @@
]]
-- require "alyxlib.math.common"
-local version = "v1.0.0"
+local version = "v1.0.1"
---@class QAngle
local meta = getmetatable(QAngle())
diff --git a/scripts/vscripts/alyxlib/extensions/string.lua b/scripts/vscripts/alyxlib/extensions/string.lua
index c153d4b..3247551 100644
--- a/scripts/vscripts/alyxlib/extensions/string.lua
+++ b/scripts/vscripts/alyxlib/extensions/string.lua
@@ -1,5 +1,5 @@
--[[
- v1.3.1
+ v2.0.0
https://github.com/FrostSource/alyxlib
Provides string class extension methods.
@@ -9,7 +9,7 @@
require "alyxlib.extensions.string"
]]
-local version = "v1.3.1"
+local version = "v2.0.0"
---Escapes special characters in a string.
---@param s string # The string to escape
diff --git a/scripts/vscripts/alyxlib/extensions/template.lua b/scripts/vscripts/alyxlib/extensions/template.lua
index dbb0c3e..2846487 100644
--- a/scripts/vscripts/alyxlib/extensions/template.lua
+++ b/scripts/vscripts/alyxlib/extensions/template.lua
@@ -1,5 +1,5 @@
--[[
- v1.0.0
+ v1.0.1
https://github.com/FrostSource/alyxlib
Provides point_template extension methods.
@@ -9,7 +9,7 @@
require "alyxlib.extensions.template"
]]
-local version = "v1.0.0"
+local version = "v1.0.1"
---
---Spawn the template at the IO caller position, rotated around the caller.
diff --git a/scripts/vscripts/alyxlib/extensions/vector.lua b/scripts/vscripts/alyxlib/extensions/vector.lua
index 9d31290..dd9e9e0 100644
--- a/scripts/vscripts/alyxlib/extensions/vector.lua
+++ b/scripts/vscripts/alyxlib/extensions/vector.lua
@@ -1,5 +1,5 @@
--[[
- v1.3.0
+ v1.3.1
https://github.com/FrostSource/alyxlib
Provides Vector class extension methods.
@@ -10,7 +10,7 @@
]]
require "alyxlib.math.common"
-local version = "v1.3.0"
+local version = "v1.3.1"
---@class Vector
local meta = getmetatable(Vector())
diff --git a/scripts/vscripts/alyxlib/globals.lua b/scripts/vscripts/alyxlib/globals.lua
index 3de61fe..645c537 100644
--- a/scripts/vscripts/alyxlib/globals.lua
+++ b/scripts/vscripts/alyxlib/globals.lua
@@ -1,5 +1,5 @@
--[[
- v2.7.0
+ v2.8.0
https://github.com/FrostSource/alyxlib
Provides common global functions used throughout extravaganza libraries.
@@ -12,7 +12,7 @@
-- These are expected by globals
require 'alyxlib.utils.common'
-local _version = "v2.7.0"
+local _version = "v2.8.0"
---
---A registered AlyxLib addon.
diff --git a/scripts/vscripts/alyxlib/helpers/animation.lua b/scripts/vscripts/alyxlib/helpers/animation.lua
index 894d54b..5405f17 100644
--- a/scripts/vscripts/alyxlib/helpers/animation.lua
+++ b/scripts/vscripts/alyxlib/helpers/animation.lua
@@ -1,5 +1,5 @@
--[[
- v1.2.0
+ v1.2.1
https://github.com/FrostSource/alyxlib
Provides functionality for animating getter/setter entity methods using curves.
@@ -15,7 +15,7 @@
---@diagnostic disable: undefined-field
---@class Animation
Animation = {}
-Animation.version = "v1.2.0"
+Animation.version = "v1.2.1"
local localBounceOut = function(a, b, t)
local normalizedT = t
diff --git a/scripts/vscripts/alyxlib/helpers/easyconvars.lua b/scripts/vscripts/alyxlib/helpers/easyconvars.lua
index be37a81..5266540 100644
--- a/scripts/vscripts/alyxlib/helpers/easyconvars.lua
+++ b/scripts/vscripts/alyxlib/helpers/easyconvars.lua
@@ -1,5 +1,5 @@
--[[
- v2.0.0
+ v2.1.0
https://github.com/FrostSource/alyxlib
Allows for quick creation of convars which support persistence saving, checking GlobalSys for default values, and callbacks on value change.
@@ -16,7 +16,7 @@ require "alyxlib.globals"
---
---@class EasyConvars
EasyConvars = {}
-EasyConvars.version = "v2.0.0"
+EasyConvars.version = "v2.1.0"
---
---Data of a registered convar.
diff --git a/scripts/vscripts/alyxlib/init.lua b/scripts/vscripts/alyxlib/init.lua
index 1144de7..545b4b7 100644
--- a/scripts/vscripts/alyxlib/init.lua
+++ b/scripts/vscripts/alyxlib/init.lua
@@ -1,5 +1,5 @@
--[[
- v1.2.2
+ v1.2.3
https://github.com/FrostSource/alyxlib
The main initializer script loads any standard libraries that it can find.
@@ -11,12 +11,12 @@
]]
-- Version of this file
-local version = "v1.2.2"
+local version = "v1.2.3"
---ID of AlyxLib in the Steam workshop
ALYXLIB_WORKSHOP_ID = "3329679071"
---The current version of AlyxLib as a whole
-ALYXLIB_VERSION = "v2.0.0"
+ALYXLIB_VERSION = "v3.0.0"
print("Initializing AlyxLib system ".. ALYXLIB_VERSION .." ...")
@@ -96,6 +96,7 @@ else
end
alyxlib_require "alyxlib.debug.debug_menu"
alyxlib_require "alyxlib.debug.debug_menu_cfg"
+alyxlib_require "alyxlib.debug.menus.debug_menu_alyxlib"
-- Common third-party libraries
diff --git a/scripts/vscripts/alyxlib/math/common.lua b/scripts/vscripts/alyxlib/math/common.lua
index c4c0d86..463a1b0 100644
--- a/scripts/vscripts/alyxlib/math/common.lua
+++ b/scripts/vscripts/alyxlib/math/common.lua
@@ -1,5 +1,5 @@
--[[
- v1.3.0
+ v2.0.0
https://github.com/FrostSource/alyxlib
Extends the math library with useful functions.
@@ -9,7 +9,7 @@
require "alyxlib.math.common"
]]
-local version = "v1.3.0"
+local version = "v2.0.0"
---
---Gets the sign of a number.
diff --git a/scripts/vscripts/alyxlib/math/weighted_random.lua b/scripts/vscripts/alyxlib/math/weighted_random.lua
index 95345b1..ebe3459 100644
--- a/scripts/vscripts/alyxlib/math/weighted_random.lua
+++ b/scripts/vscripts/alyxlib/math/weighted_random.lua
@@ -1,5 +1,5 @@
--[[
- v1.2.4
+ v1.2.5
https://github.com/FrostSource/alyxlib
Weighted random allows you to assign chances to tables keys.
@@ -9,7 +9,7 @@
require "alyxlib.math.weighted_random"
]]
-local version = "v1.2.4"
+local version = "v1.2.5"
---
---A list of tables with associated weights.
diff --git a/scripts/vscripts/alyxlib/panorama/core.lua b/scripts/vscripts/alyxlib/panorama/core.lua
index 90886b8..12becdf 100644
--- a/scripts/vscripts/alyxlib/panorama/core.lua
+++ b/scripts/vscripts/alyxlib/panorama/core.lua
@@ -1,5 +1,5 @@
--[[
- v1.1.1
+ v1.2.0
https://github.com/FrostSource/alyxlib
Panorama core library.
@@ -10,7 +10,7 @@
]]
Panorama = {}
-Panorama.version = "v1.1.1"
+Panorama.version = "v1.2.0"
---Filters text string to replace problematic characters.
---@param text string
diff --git a/scripts/vscripts/alyxlib/player/core.lua b/scripts/vscripts/alyxlib/player/core.lua
index 3aca262..74f98da 100644
--- a/scripts/vscripts/alyxlib/player/core.lua
+++ b/scripts/vscripts/alyxlib/player/core.lua
@@ -1,5 +1,5 @@
--[[
- v4.2.2
+ v4.3.0
https://github.com/FrostSource/alyxlib
Player script allows for more advanced player manipulation and easier
@@ -14,7 +14,7 @@ require "alyxlib.globals"
require "alyxlib.extensions.entity"
require "alyxlib.storage"
-local version = "v4.2.2"
+local version = "v4.3.0"
-----------------------------
-- Class extension members --
diff --git a/scripts/vscripts/alyxlib/player/events.lua b/scripts/vscripts/alyxlib/player/events.lua
index d0f7290..9a0e325 100644
--- a/scripts/vscripts/alyxlib/player/events.lua
+++ b/scripts/vscripts/alyxlib/player/events.lua
@@ -1,5 +1,5 @@
--[[
- v2.2.0
+ v2.3.0
https://github.com/FrostSource/alyxlib
Player event management and callbacks.
@@ -9,7 +9,7 @@
require "alyxlib.player.events"
]]
-local version = "v2.2.0"
+local version = "v2.3.0"
local playerData = require "alyxlib.player.data"
diff --git a/scripts/vscripts/alyxlib/player/hands.lua b/scripts/vscripts/alyxlib/player/hands.lua
index 586db69..42649dc 100644
--- a/scripts/vscripts/alyxlib/player/hands.lua
+++ b/scripts/vscripts/alyxlib/player/hands.lua
@@ -1,5 +1,5 @@
--[[
- v1.3.0
+ v1.3.1
https://github.com/FrostSource/alyxlib
Code for player hands.
@@ -9,7 +9,7 @@
require "alyxlib.player.hands"
]]
-local version = "v1.3.0"
+local version = "v1.3.1"
---
---Merges an existing prop with this hand.
diff --git a/scripts/vscripts/alyxlib/player/wrist_attachments.lua b/scripts/vscripts/alyxlib/player/wrist_attachments.lua
index e9de56a..b73b391 100644
--- a/scripts/vscripts/alyxlib/player/wrist_attachments.lua
+++ b/scripts/vscripts/alyxlib/player/wrist_attachments.lua
@@ -1,5 +1,5 @@
--[[
- v1.2.0
+ v1.2.1
https://github.com/FrostSource/alyxlib
Wrist attachments allow you to attach entities to the player's hands
@@ -12,7 +12,7 @@
---@class WristAttachments
WristAttachments = {}
-WristAttachments.version = "v1.2.0"
+WristAttachments.version = "v1.2.1"
---@alias WristAttachmentHandType "left"|"right"|"primary"|"secondary"
diff --git a/scripts/vscripts/alyxlib/precache.lua b/scripts/vscripts/alyxlib/precache.lua
index 9d54f63..1f7431a 100644
--- a/scripts/vscripts/alyxlib/precache.lua
+++ b/scripts/vscripts/alyxlib/precache.lua
@@ -1,5 +1,5 @@
--[[
- v1.0.2
+ v1.1.0
https://github.com/FrostSource/alyxlib
Precaching can only be done with an entity attached script, so this script collects a list of assets to be automatically
@@ -21,7 +21,7 @@ end
---@class GlobalPrecache
---@overload fun(type: AlyxLibGlobalPrecacheType, path: string, spawnkeys: table?): nil
GlobalPrecache = {}
-GlobalPrecache.version = "v1.0.3"
+GlobalPrecache.version = "v1.1.0"
---@alias AlyxLibGlobalPrecacheType
---| "model_folder"
diff --git a/scripts/vscripts/alyxlib/storage.lua b/scripts/vscripts/alyxlib/storage.lua
index 657d759..d0d570c 100644
--- a/scripts/vscripts/alyxlib/storage.lua
+++ b/scripts/vscripts/alyxlib/storage.lua
@@ -1,5 +1,5 @@
--[[
- v3.2.0
+ v3.3.0
https://github.com/FrostSource/alyxlib
Helps with saving/loading values for persistency between game sessions.
@@ -58,7 +58,7 @@ end
local separator = "::"
Storage = {}
-Storage.version = "v3.2.0"
+Storage.version = "v3.3.0"
---Collection of type names associated with a class table.
---The table should have both __save() and __load() functions.
---@type table
diff --git a/scripts/vscripts/alyxlib/utils/common.lua b/scripts/vscripts/alyxlib/utils/common.lua
index a6c0476..086b098 100644
--- a/scripts/vscripts/alyxlib/utils/common.lua
+++ b/scripts/vscripts/alyxlib/utils/common.lua
@@ -1,5 +1,5 @@
--[[
- v3.1.0
+ v3.1.1
https://github.com/FrostSource/alyxlib
This file contains utility functions to help reduce repetitive code
@@ -15,7 +15,7 @@
---
---@class Util
Util = {}
-Util.version = "v3.1.0"
+Util.version = "v3.1.1"
---
---Converts `vr_tip_attachment` from a game event (1,2) into a hand id (0,1) taking primary hand into account.
diff --git a/scripts/vscripts/alyxlib/utils/enums.lua b/scripts/vscripts/alyxlib/utils/enums.lua
index 35ef286..c676929 100644
--- a/scripts/vscripts/alyxlib/utils/enums.lua
+++ b/scripts/vscripts/alyxlib/utils/enums.lua
@@ -1,5 +1,5 @@
--[[
- v1.0.1
+ v1.0.2
https://github.com/FrostSource/alyxlib
Missing enumerations.
diff --git a/scripts/vscripts/alyxlib/utils/extension_duplicate_fix.lua b/scripts/vscripts/alyxlib/utils/extension_duplicate_fix.lua
index 9d342b0..1a73585 100644
--- a/scripts/vscripts/alyxlib/utils/extension_duplicate_fix.lua
+++ b/scripts/vscripts/alyxlib/utils/extension_duplicate_fix.lua
@@ -1,5 +1,5 @@
--[[
- v1.0.0
+ v1.0.1
https://github.com/FrostSource/alyxlib
This script searches for, and deletes, any extension fixed-up entities that appear to be duplicates.
diff --git a/version.json b/version.json
index a06dd3f..2bc6d18 100644
--- a/version.json
+++ b/version.json
@@ -1,3 +1,3 @@
{
- "version": "2.0.0"
+ "version": "3.0.0"
}
\ No newline at end of file
From 1233aa3a90ffa82a142f775b973e31c4b8662a2a Mon Sep 17 00:00:00 2001
From: FrostSource
Date: Sat, 14 Mar 2026 00:54:54 +1300
Subject: [PATCH 144/146] docs: add new installer/docs & migrate assets
Added links to the new docs site and installer app.
Updated Quick Setup guide for the new app-based workflow.
Migrated project previews to local /assets folder.
---
README.md | 72 +++++++++---------
assets/addon-preview-alyx_wears_glasses.jpg | Bin 0 -> 17836 bytes
assets/addon-preview-body_holsters.jpg | Bin 0 -> 18704 bytes
.../addon-preview-combine_shoot_and_scoot.jpg | Bin 0 -> 29390 bytes
assets/addon-preview-partial_clip_storage.jpg | Bin 0 -> 21990 bytes
.../addon-preview-removable_health_vials.jpg | Bin 0 -> 19132 bytes
assets/addon-preview-resin_watch.jpg | Bin 0 -> 20783 bytes
7 files changed, 37 insertions(+), 35 deletions(-)
create mode 100644 assets/addon-preview-alyx_wears_glasses.jpg
create mode 100644 assets/addon-preview-body_holsters.jpg
create mode 100644 assets/addon-preview-combine_shoot_and_scoot.jpg
create mode 100644 assets/addon-preview-partial_clip_storage.jpg
create mode 100644 assets/addon-preview-removable_health_vials.jpg
create mode 100644 assets/addon-preview-resin_watch.jpg
diff --git a/README.md b/README.md
index 0581191..f13cf8a 100644
--- a/README.md
+++ b/README.md
@@ -1,79 +1,81 @@
-
+> [!TIP]
+> **New:** I've launched a dedicated [Documentation Website](https://frostsource.github.io/alyxlib/) and a standalone [Installer App](https://frostsource.github.io/AlyxLibInstaller/) to make setup easier!
+
---
-> [!CAUTION]\
-> This repository has been made public to create the wiki and may not work as intended until this message is removed!
-
-AlyxLib provides a set of useful Lua libraries for scripting with Half-Life: Alyx, making your development process smoother and more efficient.
+AlyxLib provides a set of useful Lua libraries for scripting with **Half-Life: Alyx**, making your development process smoother and more efficient.
Your addon is linked to AlyxLib via symbolic links, ensuring that every addon uses the same source and gets updated automatically whenever AlyxLib is updated. Plus, since your workshop item uses AlyxLib as a requirement, it will also receive any fixes without you having to reupload.
-## 📚Library overview
+## 📚 Library overview
-* Full VScript code completion using [Lua Language Server](https://luals.github.io/)
+* Full VScript code completion using [Lua Language Server](https://luals.github.io/).
* Save/Load most data types easily to any entity.
* Custom class implementation for entities, including inheritance and automatic variable saving.
* Player interaction simplification and tracking of items.
-* Panorama panel interaction, allowing sending and receiving data with Lua.
+* Send data to and from Panorama ←→ Lua.
* Easy controller input tracking with function callbacks.
* Lots of useful debugging functions and console commands.
* A fully customizable in-game debug menu.
-See the extensive wiki for full function reference and code examples.
+See the [Documentation Website](https://frostsource.github.io/alyxlib/) for component breakdowns, function references, and code examples.
-## 🚀Quick setup guide
+## 🚀 Quick setup guide
-> [!NOTE]\
-> For in-depth setup, see the [Installing AlyxLib](https://github.com/FrostSource/alyxlib/wiki/Installing-AlyxLib) wiki page
+> [!NOTE]
+> For in-depth or manual setup, see the [Getting Started Guide](https://frostsource.github.io/alyxlib/getting_started/app_installation.html).
-1. Download or clone this GitHub repository to your hlvr_addons content folder
+1. **[Download AlyxLib Installer](https://frostsource.github.io/AlyxLibInstaller/)** and run the app.
-2. Run `alyxlib.py` from the root folder
+2. Follow the setup instructions within the app to download AlyxLib.
-3. Select your addon from the list of addons shown
+3. Open your addon from the file menu and select the AlyxLib modules you wish to use.
-4. Select the type of installation you want for your addon (can be changed later)
+4. **Before uploading:** Click the `Remove For Upload` button in the app.
-5. Before uploading to the workshop, run the `on_upload.bat` that was created in your addon content folder to temporarily remove symlinks, **but do not -exit the program**
+5. Upload your addon to the workshop and set [AlyxLib](https://steamcommunity.com/sharedfiles/filedetails/?id=3329679071) as a required item.
-6. Upload your addon to the workshop and set [AlyxLib](https://steamcommunity.com/sharedfiles/filedetails/?id=3329679071) as a required item
+6. Rename the `0000000000.lua` file in `scripts/vscripts/mods/init/` to match the workshop ID of your new workshop item [(this is the same process outlined in Scalable Init Support)](https://github.com/PeterSHollander/scalable_init_support?tab=readme-ov-file#for-workshop-release)
-7. Rename the `0000000000.lua` file in `scripts/vscripts/mods/init/` to have the same number as your new workshop item [(this is the same process outlined in Scalable Init Support)](https://github.com/PeterSHollander/scalable_init_support?tab=readme-ov-file#for-workshop-release)
+7. Click `Install` in the app to restore the AlyxLib files in your addon.
-8. Finish the instructions shown in the running `on_upload.bat` program to restore the symlinks for further development
+## ❓ Need help?
-## ❓Need help?
+If you run into issues, feel free to [create an issue](https://github.com/FrostSource/alyxlib/issues).
-Please feel free to create an issue with your problem, question or suggestion.
+You can also join us on [Discord](https://discord.gg/42SC3Wyjv4) for faster responses.
-You can also join us on our [Discord server](https://discord.gg/42SC3Wyjv4) to get faster responses.
-
-## 🌟Projects using AlyxLib
+## 🌟 Projects using AlyxLib
-
-
-
-
-
-
-
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/assets/addon-preview-alyx_wears_glasses.jpg b/assets/addon-preview-alyx_wears_glasses.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..f1993e84a53b71928ca8361d9453f4088c988da7
GIT binary patch
literal 17836
zcmeIaWmKHaw