From 150ab327e85e098abe7582d348ae53bc3f917dde Mon Sep 17 00:00:00 2001
From: Wunder Wulfe <29297318+Wunder-Wulfe@users.noreply.github.com>
Date: Mon, 27 Jul 2026 20:49:02 -0300
Subject: [PATCH 01/10] Numerous bug fixes and enhancements to PAC3
Contribution for the new commands and some of the setup goes to @pingu7867
* **Part Features**
* Added `Use Range` for `Flex` and `PoseParameter` parts. Uses the ranges defined in the controllers, i.e. -1 to 1, -60 to 60, etc. Defaults off for backwards compatibility.
* Added `Clamped` for `Flex` parts. Clamps the `Weight` value into the range defined by the flex controller. Helps to avoid flexes exploding when using `Additive`, or converting angles into flex values directly without worrying about the ranges. Defaults off for backwards compatibility.
* **Library Features**
* Introduced several utility functions for facilitating / unifying flex/poseparam adjustments across PAC:
* Flex Utility:
* `pac.GetFlexBounds()` reports the range of a flex controller
* `pac.FromFlexRange()` converts values from flex ranges to 0 - 1
* `pac.ToFlexRange()` converts values from 0 - 1 to flex ranges
* `pac.SetFlexWeight()` set flex weights using the range defined by the flex controller
* `pac.GetFlexWeight()` reports flex weights using the range defined by the flex controller
* Pose Parameter Utility:
* `pac.GetPoseParameterRange()` reports the range of a pose parameter
* `pac.FromPoseParameterRange()` converts values from pose parameter ranges to 0 - 1
* `pac.ToPoseParameterRange()` converts values from 0 - 1 to pose parameter ranges
* `pac.SetPoseParameter()` set pose parameter values using the range defined by the pose parameter
* `pac.GetPoseParameter()` reports pose parameter values using the range defined by the pose parameter
* **Bug Fixes**
* `pac_override_poseparameter` and `pac_override_flexweight` correctly use the ranges defined in the controllers, as opposed to always being 0 - 1
* Pose Parameter parts not applying pose parameter values on wear unless their argument was changed
* Flex part not correctly releasing flex controllers when hidden, removed, argument changed, etc
---
lua/pac3/core/client/bones.lua | 6 +-
lua/pac3/core/client/hooks.lua | 6 +-
lua/pac3/core/client/parts/faceposer.lua | 26 ++-
lua/pac3/core/client/parts/flex.lua | 49 +++-
lua/pac3/core/client/parts/model.lua | 2 +-
lua/pac3/core/client/parts/model/entity.lua | 4 +-
lua/pac3/core/client/parts/poseparameter.lua | 24 +-
.../core/shared/entity_mutators/model.lua | 219 +++++++++++++++++-
8 files changed, 318 insertions(+), 18 deletions(-)
diff --git a/lua/pac3/core/client/bones.lua b/lua/pac3/core/client/bones.lua
index 71f3aab04..105cb11cb 100644
--- a/lua/pac3/core/client/bones.lua
+++ b/lua/pac3/core/client/bones.lua
@@ -432,14 +432,14 @@ do -- bone manipulation for boneanimlib
for id, time in pairs(ent.pac_touching_flexes) do
if not reset_scale then
- ent:SetFlexScale(1)
+ ent:SetFlexScale(1)
reset_scale = true
end
if time < pac.RealTime then
- ent:SetFlexWeight(id, 0)
+ pac.SetFlexWeight(ent, id, 0)
else
- if ent:GetFlexWeight(id) == 0 then
+ if pac.GetFlexWeight(ent, id) == 0 then
ent.pac_touching_flexes[id] = nil
end
end
diff --git a/lua/pac3/core/client/hooks.lua b/lua/pac3/core/client/hooks.lua
index 49cb5e2ab..3c3aa858c 100644
--- a/lua/pac3/core/client/hooks.lua
+++ b/lua/pac3/core/client/hooks.lua
@@ -45,8 +45,10 @@ do
local tbl = ply.pac_pose_params
if tbl then
- for _, data in pairs(tbl) do
- ply:SetPoseParameter(data.key, data.val)
+ for i, data in pairs(tbl) do
+ if ply:LookupPoseParameter(data.key) ~= -1 then
+ pac.SetPoseParameter(ply, data.key, data.val)
+ end
end
end
end
diff --git a/lua/pac3/core/client/parts/faceposer.lua b/lua/pac3/core/client/parts/faceposer.lua
index fdc9d46fd..0404c274c 100644
--- a/lua/pac3/core/client/parts/faceposer.lua
+++ b/lua/pac3/core/client/parts/faceposer.lua
@@ -33,7 +33,14 @@ BUILDER:StartStorableVars()
local i = tonumber(key:match("faceposer_flex(%d+)"))
if i then
local name = ent:GetFlexName(i)
+
if name then
+ weight = tonumber(weight) or 0
+
+ if part.UseRange then
+ weight = pac.ToFlexRange(ent, i, weight)
+ end
+
preset[name] = tonumber(weight)
end
end
@@ -49,6 +56,8 @@ BUILDER:StartStorableVars()
end})
:GetSet("FlexWeights", "", {hidden = true})
:GetSet("Scale", 1)
+ :GetSet("UseRange", false, {description="When enabled, the ranges defined in its Flex Controller will be used as opposed to the legacy global [0, 1] range"})
+ :GetSet("Clamped", false, {description="Limits the output range of the Flex to be within the legal ranges defined by its Flex Controller"})
:GetSet("Additive", false)
:EndStorableVars()
@@ -165,10 +174,23 @@ function PART:UpdateFlex()
for name, weight in pairs(self:GetWeightMap()) do
local id = ent:GetFlexIDByName(name)
if id then
+ if not self.UseRange then
+ weight = pac.ToFlexRange( ent, id, weight )
+ end
+
if self.Additive then
- weight = ent:GetFlexWeight(id) + weight
+ weight = pac.GetFlexWeight(ent, id) + weight
end
- ent:SetFlexWeight(id, weight)
+
+ if self.Clamped then
+ weight = math.Clamp(
+ weight,
+ ent:GetFlexBounds(id)
+ )
+ end
+
+ pac.SetFlexWeight(ent, id, weight)
+
ent.pac_touching_flexes[id] = pac.RealTime + 0.1
end
end
diff --git a/lua/pac3/core/client/parts/flex.lua b/lua/pac3/core/client/parts/flex.lua
index 4bf10990e..299eb5f8d 100644
--- a/lua/pac3/core/client/parts/flex.lua
+++ b/lua/pac3/core/client/parts/flex.lua
@@ -19,7 +19,9 @@ BUILDER:StartStorableVars()
})
BUILDER:GetSet("Weight", 0)
- BUILDER:GetSet("Additive", false)
+ BUILDER:GetSet("Additive", false)
+ BUILDER:GetSet("UseRange", false, {description="When enabled, the ranges defined in its Flex Controller will be used as opposed to the legacy global [0, 1] range"})
+ BUILDER:GetSet("Clamped", false, {description="Limits the output range of the Flex to be within the legal ranges defined by its Flex Controller"})
BUILDER:GetSet("RootOwner", false, { hide_in_editor = true })
BUILDER:EndStorableVars()
@@ -41,14 +43,49 @@ function PART:GetFlexID()
return flex and flex.i, ent
end
+function PART:OnHide()
+ local id, ent = self:GetFlexID()
+ if not id then return end
+ ent.pac_touching_flexes = ent.pac_touching_flexes or {}
+ ent.pac_touching_flexes[id] = nil
+ pac.SetFlexWeight(ent, id, 0) -- added reset!
+end
+
+function PART:OnRemove()
+ local id, ent = self:GetFlexID()
+ if not id then return end
+ ent.pac_touching_flexes = ent.pac_touching_flexes or {}
+ ent.pac_touching_flexes[id] = nil
+ pac.SetFlexWeight(ent, id, 0) -- added reset!
+end
+
function PART:OnBuildBonePositions()
local id, ent = self:GetFlexID()
if not id then return end
- local weight = self.Weight
- if self.Additive then
- weight = weight + ent:GetFlexWeight(id)
- end
- ent:SetFlexWeight(id, weight)
+
+ local weight
+
+ if self.UseRange then
+ weight = self.Weight
+ else
+ -- backwards compatibility
+ weight = pac.ToFlexRange( ent, id, self.Weight )
+ end
+
+ if self.Additive then
+ weight = weight + pac.GetFlexWeight(ent, id)
+ end
+
+ -- added clamping to prevent explosion, particularly when using additive flexes
+ if self.Clamped then
+ weight = math.Clamp(
+ weight,
+ ent:GetFlexBounds(id)
+ )
+ end
+
+ pac.SetFlexWeight(ent, id, weight)
+
ent.pac_touching_flexes = ent.pac_touching_flexes or {}
ent.pac_touching_flexes[id] = pac.RealTime + 0.1
end
diff --git a/lua/pac3/core/client/parts/model.lua b/lua/pac3/core/client/parts/model.lua
index c6fa9c76c..d477149a5 100644
--- a/lua/pac3/core/client/parts/model.lua
+++ b/lua/pac3/core/client/parts/model.lua
@@ -403,7 +403,7 @@ function PART:PreEntityDraw(ent, pos, ang)
end
end
- if self.draw_bodygroups then
+ if self.draw_bodygroups and self.OverrideBodygroups then
for _, v in ipairs(self.draw_bodygroups) do
ent:SetBodygroup(v[1], v[2])
end
diff --git a/lua/pac3/core/client/parts/model/entity.lua b/lua/pac3/core/client/parts/model/entity.lua
index 3a3b5de19..fea4426d2 100644
--- a/lua/pac3/core/client/parts/model/entity.lua
+++ b/lua/pac3/core/client/parts/model/entity.lua
@@ -18,6 +18,8 @@ BUILDER:StartStorableVars()
:PropertyOrder("Name")
:PropertyOrder("Hide")
:PropertyOrder("ParentName")
+ :GetSet("OverrideBodygroups", true, {description = "This part can change your rendered bodygroups. But there are now console commands to change your bodygroups, flexes and poseparams to save up on proxy costs and such.\nYou'll need this setting turned off to stop the entity from overwriting your bodygroups...\n\nRead up more in the console:\npac_override_bodygroup\npac_override_flexweights\npac_override_poseparameter"})
+
:SetPropertyGroup("appearance")
:GetSet("NoDraw", false)
:GetSet("DrawShadow", true)
@@ -277,4 +279,4 @@ function PART:OnThink()
end
end
-BUILDER:Register()
\ No newline at end of file
+BUILDER:Register()
diff --git a/lua/pac3/core/client/parts/poseparameter.lua b/lua/pac3/core/client/parts/poseparameter.lua
index 35f1b0685..f8c2ebf4e 100644
--- a/lua/pac3/core/client/parts/poseparameter.lua
+++ b/lua/pac3/core/client/parts/poseparameter.lua
@@ -9,6 +9,7 @@ PART.Icon = 'icon16/disconnect.png'
BUILDER:StartStorableVars()
BUILDER:GetSet("PoseParameter", "", {enums = function(part) return part:GetPoseParameterList() end})
BUILDER:GetSet("Range", 0)
+ BUILDER:GetSet("UseRange", false, {description="Limits the output range of the Pose Parameter to be within the legal ranges defined by the model"})
BUILDER:EndStorableVars()
function PART:GetNiceName()
@@ -49,7 +50,22 @@ function PART:UpdateParams()
local data = self.pose_params[self.PoseParameter]
if data then
- local num = Lerp((self.Range + 1) / 2, data.range[1] or 0, data.range[2] or 1)
+ local num
+
+ if self.UseRange then
+ num = self.Range
+ else
+ -- backwards compatibility; reverts the math in the new setter
+ -- old calculation
+
+ num = Lerp((self.Range + 1) / 2, data.range[1] or 0, data.range[2] or 1)
+
+ num = pac.ToPoseParameterRange(
+ ent,
+ data.name,
+ num
+ )
+ end
ent.pac_pose_params = ent.pac_pose_params or {}
ent.pac_pose_params[self.UniqueID] = ent.pac_pose_params[self.UniqueID] or {}
@@ -57,11 +73,15 @@ function PART:UpdateParams()
ent.pac_pose_params[self.UniqueID].key = data.name
ent.pac_pose_params[self.UniqueID].val = num
- ent:SetPoseParameter(data.name, num)
+ pac.SetPoseParameter(ent, data.name, num)
end
end
end
+function PART:OnBuildBonePositions()
+ self:UpdateParams()
+end
+
function PART:OnHide()
local ent = self:GetOwner()
diff --git a/lua/pac3/core/shared/entity_mutators/model.lua b/lua/pac3/core/shared/entity_mutators/model.lua
index 0fa531979..8d44462b5 100644
--- a/lua/pac3/core/shared/entity_mutators/model.lua
+++ b/lua/pac3/core/shared/entity_mutators/model.lua
@@ -68,4 +68,221 @@ function MUTATOR:Mutate(path)
end
end
-pac.emut.Register(MUTATOR)
\ No newline at end of file
+pac.emut.Register(MUTATOR)
+
+-- Inverse of Lerp()
+local function InvLerp(t, from, to)
+ if from==to then return from end
+ return (t - from) / (to - from)
+end
+
+-- Returns flex bounds, or default [0, 1] as a failsafe
+local function GetFlexBounds(entity, flex)
+ local min, max = entity:GetFlexBounds(flex)
+ return min or 0, max or 1
+end
+pac.GetFlexBounds = GetFlexBounds
+
+-- Convert a flex weight value from [range_min, range_max] into [0, 1]
+local function FromFlexRange(entity, flex, weight)
+ return InvLerp( weight, GetFlexBounds(entity, flex) )
+end
+pac.FromFlexRange = FromFlexRange
+
+-- Convert a flex weight value from [0, 1] into [range_min, range_max]
+local function ToFlexRange(entity, flex, weight)
+ return Lerp( weight, GetFlexBounds(entity, flex) )
+end
+pac.ToFlexRange = ToFlexRange
+
+-- Set flex weight, using the flex controller defined range
+local function SetFlexWeight(entity, flex, weight)
+ entity:SetFlexWeight(
+ flex,
+ FromFlexRange( entity, flex, weight )
+ )
+end
+pac.SetFlexWeight = SetFlexWeight
+
+-- Get flex weight, using the flex controller defined range
+local function GetFlexWeight(entity, flex)
+ return ToFlexRange( entity, flex, entity:GetFlexWeight(flex) )
+end
+pac.GetFlexWeight = GetFlexWeight
+
+-- Returns pose parameter bounds, or default [0, 1] as a failsafe
+local function GetPoseParameterRange(entity, parameter)
+ local min, max = entity:GetPoseParameterRange(parameter)
+ return min or 0, max or 1
+end
+pac.GetPoseParameterRange = GetPoseParameterRange
+
+
+-- Convert a pose parameter value from [range_min, range_max] into [0, 1]
+local function FromPoseParameterRange(entity, parameter, value)
+ return InvLerp( value, GetPoseParameterRange(entity, parameter) )
+end
+pac.FromPoseParameterRange = FromPoseParameterRange
+
+-- Convert a pose parameter value from [0, 1] into [range_min, range_max]
+local function ToPoseParameterRange(entity, parameter, value)
+ return Lerp( value, GetPoseParameterRange(entity, parameter) )
+end
+pac.ToPoseParameterRange = ToPoseParameterRange
+
+-- Set pose parameter, using the pose parameter's defined range
+local function SetPoseParameter(entity, parameter, value)
+ entity:SetPoseParameter(
+ parameter,
+ FromPoseParameterRange( entity, parameter, value )
+ )
+end
+pac.SetPoseParameter = SetPoseParameter
+
+-- Get pose parameter, using the pose parameter's defined range
+local function GetPoseParameter(entity, parameter)
+ return ToPoseParameterRange( entity, parameter, entity:GetPoseParameter(parameter) )
+end
+pac.GetPoseParameter = GetPoseParameter
+
+--extras to control other aspects of the model serverside. it's an adjacent functionality to editing your model
+--to let simple MDLs work better with pac-disabled and renderdistance cases
+if SERVER then
+ concommand.Add("pac_override_bodygroup", function(ply, name, args, args_str)
+ if not ply:IsValid() then return end
+ if not GetConVar("pac_modifier_model"):GetBool() then return end
+ local function helptext()
+ for i,tbl in ipairs(ply:GetBodyGroups()) do
+ ply:PrintMessage(HUD_PRINTCONSOLE, "[" .. tbl.id .. "] " .. tbl.name)
+ if table.Count(tbl.submodels) > 1 then
+ for i2=0, table.Count(tbl.submodels) - 1 do
+ local selected = ""
+ if i2 == ply:GetBodygroup(tbl.id) then selected = " [active]" end
+ ply:PrintMessage(HUD_PRINTCONSOLE, " [" .. i2 .. "] " .. tbl.submodels[i2] .. selected)
+ end
+ end
+ ply:PrintMessage(HUD_PRINTCONSOLE, "\n")
+ end
+ end
+ if not args[1] then
+ helptext()
+ elseif args[1] == "^" or args[1] == "__RESET__" then
+ ply:SetNWBool("pac_overriding_bodygroups", false)
+ end
+ if args[1] and args[2] then
+ local id = ply:FindBodygroupByName(args[1])
+ if id == -1 then ply:PrintMessage(HUD_PRINTCONSOLE, "invalid bodygroup!") helptext() return end
+
+ if args[2] == "+" then
+ ply:SetBodygroup(id,(ply:GetBodygroup(id)+1) % (ply:GetBodygroupCount(id)))
+ elseif args[2] == "-" then
+ ply:SetBodygroup(id,(ply:GetBodygroup(id)-1) % (ply:GetBodygroupCount(id)))
+ elseif isnumber(tonumber(args[2])) then
+ ply:SetBodygroup(id, -1)
+ ply:SetBodygroup(id, tonumber(args[2]))
+ end
+
+ ply:SetNWBool("pac_overriding_bodygroups", true)
+ elseif args[1] then
+ ply:SetBodyGroups(args[1])
+ ply:SetNWBool("pac_overriding_bodygroups", true)
+ end
+ end, nil, "sends out a request to change your playermodel's bodygroups, but stops your entity parts from changing bodygroups")
+
+ concommand.Add("pac_override_flexweight", function(ply, name, args, args_str)
+ if not ply:IsValid() then return end
+ if not GetConVar("pac_modifier_model"):GetBool() then return end
+ local function helptext()
+ for i=0,ply:GetFlexNum()-1 do
+ ply:PrintMessage(HUD_PRINTCONSOLE, "[" .. i .. "] " .. ply:GetFlexName(i))
+ end
+ end
+ if not args[1] then
+ helptext()
+ elseif args[1] == "^" or args[1] == "__RESET__" then
+ for i=0,ply:GetFlexNum()-1 do
+ SetFlexWeight(ply, i, 0)
+ end
+ end
+ if args[1] and args[2] then
+ local id = ply:GetFlexIDByName(args[1])
+ if id == nil then return end
+
+ if args[2] == "toggle" then
+ if GetFlexWeight(ply, id) < 0.5 then
+ SetFlexWeight(ply, id, 1)
+ else
+ SetFlexWeight(ply, id, 0)
+ end
+ elseif isnumber(tonumber(args[2])) then
+ SetFlexWeight(ply, id, tonumber(args[2]))
+ end
+ end
+ end, nil, "sends out a request to change your playermodel's flex weights")
+
+ util.AddNetworkString("pac_update_poseparameter")
+ local function broadcast_poseparam(ply, id, value, reset)
+ net.Start("pac_update_poseparameter", true)
+ net.WriteUInt(id, 5)
+ net.WriteInt(value * 100, 16)
+ net.WriteBool(reset)
+ net.WriteEntity(ply)
+ net.Broadcast()
+ end
+
+ concommand.Add("pac_override_poseparameter", function(ply, name, args, args_str)
+ if not ply:IsValid() then return end
+ if not GetConVar("pac_modifier_model"):GetBool() then return end
+ local function helptext()
+ for i=0,ply:GetNumPoseParameters()-1 do
+ local min, max = ply:GetPoseParameterRange(i)
+ ply:PrintMessage(HUD_PRINTCONSOLE, "[" .. i .. "] " .. ply:GetPoseParameterName(i) .. " {"..min.."-"..max.."}")
+ end
+ end
+ if not args[1] then
+ helptext()
+ elseif args[1] == "^" or args[1] == "__RESET__" then
+ for i=0,ply:GetNumPoseParameters()-1 do
+ broadcast_poseparam(ply, id, 0, true)
+ end
+ end
+ if args[1] and args[2] then
+ local id = ply:LookupPoseParameter(args[1])
+ if id == -1 then return end
+ local n = tonumber(args[2])
+ if isnumber(n) then
+ SetPoseParameter( ply, id, n )
+ broadcast_poseparam(ply, id, n, false)
+ elseif args[2] == "reset" then
+ broadcast_poseparam(ply, id, 0, true)
+ end
+ end
+ end, nil, "sends out a request to change your playermodel's pose parameters")
+
+else
+ net.Receive("pac_update_poseparameter", function()
+ local id = net.ReadUInt(5)
+ local value = net.ReadInt(16) / 100
+ local reset = net.ReadBool()
+ local ent = net.ReadEntity()
+ local name = ent:GetPoseParameterName(id)
+ local hookname = "manual_"..name
+ ent.pac_pose_params = ent.pac_pose_params or {}
+
+ if reset then
+ ent.pac_pose_params[hookname] = nil
+
+ SetPoseParameter(ent, id, 0)
+
+ ent:InvalidateBoneCache()
+ else
+ ent.pac_pose_params[hookname] = ent.pac_pose_params[hookname] or {}
+ ent.pac_pose_params[hookname].key = name
+ ent.pac_pose_params[hookname].val = value
+
+ SetPoseParameter(ent, id, value)
+
+ ent:InvalidateBoneCache()
+ end
+ end)
+end
From 4445d75269d2806cf77ae51a39de6b8cfba56a08 Mon Sep 17 00:00:00 2001
From: Wunder Wulfe <29297318+Wunder-Wulfe@users.noreply.github.com>
Date: Tue, 4 Aug 2026 23:46:43 -0300
Subject: [PATCH 02/10] Expanded Proxy Expressions
* Fixed blacklist to use frontier patterns instead of the previous pattern matching structure, which would erroneously detect statements like `owner_velocity_forward()` due to `_for` satisfying the previous `[%s%p]for` match. Now matches against `%f[%a](for)%f[%A]`
* Enables additional compilation mode, allowing use of local variables, if statements, and return statements
* Provides alternative ways of computing proxies optimally, by re-using calculation results, or short circuit evaluations / early exits via if statements
* Prohibits function declarations, loops, and modification of global variables for security/safety reasons. Modifying, assigning to, or creating globals will result in an error
* Improved readability / structure of expressions.lua code
* Moved repetitive code into functions, and simplified logic
* Additional logic moved into functions to make iteration and testing easier
---
lua/pac3/libraries/expression.lua | 82 +++++++++++++++++++++++++------
1 file changed, 68 insertions(+), 14 deletions(-)
diff --git a/lua/pac3/libraries/expression.lua b/lua/pac3/libraries/expression.lua
index f44ad9655..328bcbbc4 100644
--- a/lua/pac3/libraries/expression.lua
+++ b/lua/pac3/libraries/expression.lua
@@ -37,7 +37,58 @@ local lib = {
round = math.Round,
}
-local blacklist = {"repeat", "until", "function", "end"}
+local blacklist = {
+ "function";
+ "for"; "break";
+ "while"; "do";
+ "repeat"; "until";
+}
+
+local function_intro = "local IN = (...); "
+
+local function_formats = {
+ function_intro .. "return %s";
+ function_intro .. "%s"; -- allows return semantics
+}
+
+local function TryCompile(code, identifier)
+ local result = CompileString(code, identifier, false)
+
+ return not isstring(result), result
+end
+
+local function CompileStringAdvanced(code, identifier)
+ local success, func = false, nil
+
+ for _, structure in pairs(function_formats) do
+ success, func = TryCompile(structure:format(code), identifier)
+
+ print(structure, code)
+
+ if success then break end
+ end
+
+ return success, func
+end
+
+local function readonlyError()
+ error("Not allowed to assign to globals", 3)
+end
+
+local function makeReadonly(t)
+ return setmetatable(
+ {},
+ {
+ __index = t,
+ __newindex = readonlyError,
+ __metatable = "This metatable is locked."
+ }
+ )
+end
+
+local function copyInto(t1, t2)
+ for k,v in pairs(t1) do t2[k] = v end
+end
local function compile_expression(str, extra_lib)
for _, word in pairs(blacklist) do
@@ -46,25 +97,28 @@ local function compile_expression(str, extra_lib)
end
end
- local functions = {}
+ local success, func = CompileStringAdvanced(str, "pac_expression")
- for k,v in pairs(lib) do functions[k] = v end
-
- if extra_lib then
- for k,v in pairs(extra_lib) do functions[k] = v end
- end
+ if success then
+ local functions = {}
- functions.select = select
- str = "local IN = select(1, ...) return " .. str
+ copyInto(lib, functions)
- local func = CompileString(str, "pac_expression", false)
+ if extra_lib then
+ copyInto(extra_lib, functions)
+ end
- if isstring(func) then
- return false, func
- else
- setfenv(func, functions)
+ functions.select = select
+
+ setfenv(
+ func,
+ makeReadonly(functions)
+ )
+
return true, func
end
+
+ return false, func
end
return compile_expression
From e9c2f242032ccd08dbd17d325a57293dc724aa04 Mon Sep 17 00:00:00 2001
From: Wunder Wulfe <29297318+Wunder-Wulfe@users.noreply.github.com>
Date: Wed, 5 Aug 2026 00:02:44 -0300
Subject: [PATCH 03/10] Updated expressions.lua with the correct file
I made changes in the addon and forgot to copy them to the repo!
---
lua/pac3/libraries/expression.lua | 35 ++++++++++++++++++++++++-------
1 file changed, 27 insertions(+), 8 deletions(-)
diff --git a/lua/pac3/libraries/expression.lua b/lua/pac3/libraries/expression.lua
index 328bcbbc4..216fd137d 100644
--- a/lua/pac3/libraries/expression.lua
+++ b/lua/pac3/libraries/expression.lua
@@ -1,4 +1,3 @@
-
local lib = {
PI = math.pi,
rand = math.random,
@@ -44,6 +43,14 @@ local blacklist = {
"repeat"; "until";
}
+-- convert blacklist items into patterns to match
+for k, item in pairs(blacklist) do
+ -- uses more efficient / conclusive frontier pattern syntax
+ -- frontier matches transition into and out of sets
+ -- in this case, matching transition into letters and then out of letters (to match whole words and not partials)
+ blacklist[k] = ("%%f[%%a](%s)%%f[%%A]"):format(item)
+end
+
local function_intro = "local IN = (...); "
local function_formats = {
@@ -63,8 +70,6 @@ local function CompileStringAdvanced(code, identifier)
for _, structure in pairs(function_formats) do
success, func = TryCompile(structure:format(code), identifier)
- print(structure, code)
-
if success then break end
end
@@ -72,7 +77,7 @@ local function CompileStringAdvanced(code, identifier)
end
local function readonlyError()
- error("Not allowed to assign to globals", 3)
+ error("Not allowed to assign to globals", 2)
end
local function makeReadonly(t)
@@ -90,12 +95,26 @@ local function copyInto(t1, t2)
for k,v in pairs(t1) do t2[k] = v end
end
-local function compile_expression(str, extra_lib)
- for _, word in pairs(blacklist) do
- if str:find("[%p%s]" .. word) or str:find(word .. "[%p%s]") then
- return false, string.format("illegal characters used %q", word)
+local function checkBlacklist(code)
+ local str
+
+ for _, word in pairs(blacklist) do
+ str = code:match(word)
+
+ if str then
+ return str
end
end
+
+ return nil
+end
+
+local function compile_expression(str, extra_lib)
+ local illegalWord = checkBlacklist(str)
+
+ if illegalWord then
+ return false, string.format("illegal characters used %q", illegalWord)
+ end
local success, func = CompileStringAdvanced(str, "pac_expression")
From 894af225ad43f2553763da8d012976bdf2686d57 Mon Sep 17 00:00:00 2001
From: pingu7867
Date: Fri, 7 Aug 2026 21:44:37 -0400
Subject: [PATCH 04/10] fixes and adjustments for model override commands
invert the bool design for bodygroup override so that non-entity model parts, which would have this property nil, don't break their bodygroup-setting
removed SetNWBools because the override bodygroup behavior is now a part property instead
reapply flexes to the ragdoll on death
but there's a userinfo cvar to skip this, as it needs to be networked! since there's a cost there are also limits for what gets sent. nothing below weight 0.1, no more than 10 flexes
reset command modes now implemented for bodygroup override, reverts to playermodel's bodygroups cvar set
---
lua/pac3/core/client/parts/model.lua | 2 +-
lua/pac3/core/client/parts/model/entity.lua | 2 +-
.../core/shared/entity_mutators/model.lua | 196 +++++++++++++-----
3 files changed, 150 insertions(+), 50 deletions(-)
diff --git a/lua/pac3/core/client/parts/model.lua b/lua/pac3/core/client/parts/model.lua
index d477149a5..238445015 100644
--- a/lua/pac3/core/client/parts/model.lua
+++ b/lua/pac3/core/client/parts/model.lua
@@ -403,7 +403,7 @@ function PART:PreEntityDraw(ent, pos, ang)
end
end
- if self.draw_bodygroups and self.OverrideBodygroups then
+ if self.draw_bodygroups and not self.IgnoreBodygroups then
for _, v in ipairs(self.draw_bodygroups) do
ent:SetBodygroup(v[1], v[2])
end
diff --git a/lua/pac3/core/client/parts/model/entity.lua b/lua/pac3/core/client/parts/model/entity.lua
index fea4426d2..2d81650a1 100644
--- a/lua/pac3/core/client/parts/model/entity.lua
+++ b/lua/pac3/core/client/parts/model/entity.lua
@@ -18,7 +18,7 @@ BUILDER:StartStorableVars()
:PropertyOrder("Name")
:PropertyOrder("Hide")
:PropertyOrder("ParentName")
- :GetSet("OverrideBodygroups", true, {description = "This part can change your rendered bodygroups. But there are now console commands to change your bodygroups, flexes and poseparams to save up on proxy costs and such.\nYou'll need this setting turned off to stop the entity from overwriting your bodygroups...\n\nRead up more in the console:\npac_override_bodygroup\npac_override_flexweights\npac_override_poseparameter"})
+ :GetSet("IgnoreBodygroups", false, {description = "This part can change your rendered bodygroups. But there are now console commands to change your bodygroups, flexes and poseparams to save up on proxy costs and such.\nYou'll need this setting turned on to stop the entity from overwriting your bodygroups...\n\nRead up more in the console:\npac_override_bodygroup\npac_override_flexweights\npac_override_poseparameter"})
:SetPropertyGroup("appearance")
:GetSet("NoDraw", false)
diff --git a/lua/pac3/core/shared/entity_mutators/model.lua b/lua/pac3/core/shared/entity_mutators/model.lua
index 8d44462b5..0f431bc79 100644
--- a/lua/pac3/core/shared/entity_mutators/model.lua
+++ b/lua/pac3/core/shared/entity_mutators/model.lua
@@ -89,7 +89,7 @@ local function FromFlexRange(entity, flex, weight)
end
pac.FromFlexRange = FromFlexRange
--- Convert a flex weight value from [0, 1] into [range_min, range_max]
+-- Convert a flex weight value from [0, 1] into [range_min, range_max]
local function ToFlexRange(entity, flex, weight)
return Lerp( weight, GetFlexBounds(entity, flex) )
end
@@ -98,7 +98,7 @@ pac.ToFlexRange = ToFlexRange
-- Set flex weight, using the flex controller defined range
local function SetFlexWeight(entity, flex, weight)
entity:SetFlexWeight(
- flex,
+ flex,
FromFlexRange( entity, flex, weight )
)
end
@@ -124,7 +124,7 @@ local function FromPoseParameterRange(entity, parameter, value)
end
pac.FromPoseParameterRange = FromPoseParameterRange
--- Convert a pose parameter value from [0, 1] into [range_min, range_max]
+-- Convert a pose parameter value from [0, 1] into [range_min, range_max]
local function ToPoseParameterRange(entity, parameter, value)
return Lerp( value, GetPoseParameterRange(entity, parameter) )
end
@@ -133,7 +133,7 @@ pac.ToPoseParameterRange = ToPoseParameterRange
-- Set pose parameter, using the pose parameter's defined range
local function SetPoseParameter(entity, parameter, value)
entity:SetPoseParameter(
- parameter,
+ parameter,
FromPoseParameterRange( entity, parameter, value )
)
end
@@ -148,6 +148,74 @@ pac.GetPoseParameter = GetPoseParameter
--extras to control other aspects of the model serverside. it's an adjacent functionality to editing your model
--to let simple MDLs work better with pac-disabled and renderdistance cases
if SERVER then
+ pac.player_submodel_mutations = {}
+
+ util.AddNetworkString("pac_update_flexweight")
+ local function broadcast_flexweight(ply, id, value)
+ if ply:GetInfoNum("pac_override_flexweight_mirrored_on_ragdoll", 0) ~= 1 then return end
+ if pac.player_submodel_mutations and pac.player_submodel_mutations[ply] then
+ if not pac.player_submodel_mutations[ply]["flex"] then return end
+ if not pac.player_submodel_mutations[ply]["flex"][id] then return end
+ net.Start("pac_update_flexweight", true)
+ net.WriteUInt(id, 6)
+ net.WriteInt(value * 100, 16)
+ net.WriteEntity(ply)
+ net.Broadcast()
+ end
+ end
+
+ local function update_register(ply, mutation, key, value)
+ pac.player_submodel_mutations[ply] = pac.player_submodel_mutations[ply] or {
+ bodygroup = {},
+ flex = {},
+ poseparameter = {},
+ }
+ if pac.player_submodel_mutations[ply][mutation] then
+ pac.player_submodel_mutations[ply][mutation][key] = value
+ end
+ end
+ local function reapply_modifications(ent, owner, duplicate_to_ragdoll)
+
+ if not pac.player_submodel_mutations then return end
+ if not pac.player_submodel_mutations[owner] then return end
+
+
+ --bodygroups are already networked to the ragdoll
+ if pac.player_submodel_mutations[owner]["bodygroup"] then
+ for k,v in pairs(pac.player_submodel_mutations[owner]["bodygroup"]) do
+ pac.SetPoseParameter(ent, k, v)
+ end
+ end
+
+ --poseparameters I'm not sure
+ if pac.player_submodel_mutations[owner]["poseparameter"] then
+ for k,v in pairs(pac.player_submodel_mutations[owner]["poseparameter"]) do
+ pac.SetPoseParameter(ent, k, v)
+ end
+ end
+
+ --flexes need to be reapplied
+ if pac.player_submodel_mutations[owner]["flex"] then
+ if duplicate_to_ragdoll then
+ local limit = 10
+ local msg = 0
+ local min_value = 0.1
+ for k,v in pairs(pac.player_submodel_mutations[owner]["flex"]) do
+ if math.abs(v) < min_value then continue end
+ if msg > limit then break end
+ pac.SetFlexWeight(ent, k, v)
+ broadcast_flexweight(owner, k, v)
+ msg = msg + 1
+ end
+ return
+ else
+ for k,v in pairs(pac.player_submodel_mutations[owner]["flex"]) do
+ pac.SetFlexWeight(owner, k, v)
+ end
+ end
+ end
+ end
+
concommand.Add("pac_override_bodygroup", function(ply, name, args, args_str)
if not ply:IsValid() then return end
if not GetConVar("pac_modifier_model"):GetBool() then return end
@@ -166,59 +234,74 @@ if SERVER then
end
if not args[1] then
helptext()
- elseif args[1] == "^" or args[1] == "__RESET__" then
- ply:SetNWBool("pac_overriding_bodygroups", false)
+ elseif args[1] == "^" or args[1] == "reset" then
+ for i, str in ipairs(string.Split(ply:GetInfo("cl_playerbodygroups")," ")) do
+ ply:SetBodygroup(i-1, tonumber(str))
+ end
end
if args[1] and args[2] then
local id = ply:FindBodygroupByName(args[1])
if id == -1 then ply:PrintMessage(HUD_PRINTCONSOLE, "invalid bodygroup!") helptext() return end
if args[2] == "+" then
- ply:SetBodygroup(id,(ply:GetBodygroup(id)+1) % (ply:GetBodygroupCount(id)))
+ local val = (ply:GetBodygroup(id)+1) % (ply:GetBodygroupCount(id))
+ ply:SetBodygroup(id,val)
+ update_register(ply, "bodygroup", id, val)
elseif args[2] == "-" then
- ply:SetBodygroup(id,(ply:GetBodygroup(id)-1) % (ply:GetBodygroupCount(id)))
+ local val = (ply:GetBodygroup(id)-1) % (ply:GetBodygroupCount(id))
+ ply:SetBodygroup(id,val)
+ update_register(ply, "bodygroup", id, val)
+ elseif args[2] == "toggle" then
+ if ply:GetBodygroup(id) >= 1 then
+ ply:SetBodygroup(id, 0)
+ update_register(ply, "bodygroup", id, 0)
+ else
+ ply:SetBodygroup(id, 1)
+ update_register(ply, "bodygroup", id, 1)
+ end
elseif isnumber(tonumber(args[2])) then
ply:SetBodygroup(id, -1)
ply:SetBodygroup(id, tonumber(args[2]))
+ update_register(ply, "bodygroup", id, tonumber(args[2]))
end
-
- ply:SetNWBool("pac_overriding_bodygroups", true)
elseif args[1] then
ply:SetBodyGroups(args[1])
- ply:SetNWBool("pac_overriding_bodygroups", true)
end
- end, nil, "sends out a request to change your playermodel's bodygroups, but stops your entity parts from changing bodygroups")
+ end, nil, "sends out a request to change your playermodel's bodygroups, but you'll need to stop your entity parts from changing bodygroups\n\ne.g.\npac_override_bodygroup 1pac_override_bodygroup 'Head Dress' 1")
concommand.Add("pac_override_flexweight", function(ply, name, args, args_str)
if not ply:IsValid() then return end
if not GetConVar("pac_modifier_model"):GetBool() then return end
local function helptext()
- for i=0,ply:GetFlexNum()-1 do
+ for i=1,ply:GetFlexNum()-1 do
ply:PrintMessage(HUD_PRINTCONSOLE, "[" .. i .. "] " .. ply:GetFlexName(i))
end
end
if not args[1] then
helptext()
- elseif args[1] == "^" or args[1] == "__RESET__" then
+ elseif args[1] == "^" or args[1] == "reset" then
for i=0,ply:GetFlexNum()-1 do
- SetFlexWeight(ply, i, 0)
+ pac.SetFlexWeight(ply, i, 0)
+ update_register(ply, "flex", i, nil)
end
end
if args[1] and args[2] then
- local id = ply:GetFlexIDByName(args[1])
+ local id = ply:GetFlexIDByName(args[1]) or tonumber(args[1])
if id == nil then return end
if args[2] == "toggle" then
- if GetFlexWeight(ply, id) < 0.5 then
- SetFlexWeight(ply, id, 1)
- else
- SetFlexWeight(ply, id, 0)
- end
+ local val = ply:GetFlexWeight(id) < 0.5 and 1 or 0
+ pac.SetFlexWeight(ply, id, -1)
+ pac.SetFlexWeight(ply, id, val)
+ update_register(ply, "flex", id, val)
+ broadcast_flexweight(ply, id, val)
elseif isnumber(tonumber(args[2])) then
- SetFlexWeight(ply, id, tonumber(args[2]))
+ pac.SetFlexWeight(ply, id, tonumber(args[2]))
+ update_register(ply, "flex", id, tonumber(args[2]))
+ broadcast_flexweight(ply, id, tonumber(args[2]))
end
end
- end, nil, "sends out a request to change your playermodel's flex weights")
+ end, nil, "sends out a request to change your playermodel's flex weights\n\ne.g.\npac_override_flexweight blink-happy 1\npac_override_flexweight ^\npac_override_flexweight blink toggle\n\nthe toggle mode switches between 0 and 1, depending on whether the serverside value is above 0.5")
util.AddNetworkString("pac_update_poseparameter")
local function broadcast_poseparam(ply, id, value, reset)
@@ -234,55 +317,72 @@ if SERVER then
if not ply:IsValid() then return end
if not GetConVar("pac_modifier_model"):GetBool() then return end
local function helptext()
- for i=0,ply:GetNumPoseParameters()-1 do
+ for i=1,ply:GetNumPoseParameters()-1 do
local min, max = ply:GetPoseParameterRange(i)
ply:PrintMessage(HUD_PRINTCONSOLE, "[" .. i .. "] " .. ply:GetPoseParameterName(i) .. " {"..min.."-"..max.."}")
end
end
if not args[1] then
helptext()
- elseif args[1] == "^" or args[1] == "__RESET__" then
+ elseif args[1] == "^" or args[1] == "reset" then
for i=0,ply:GetNumPoseParameters()-1 do
broadcast_poseparam(ply, id, 0, true)
+ update_register(ply, "poseparameter", id, nil)
end
end
if args[1] and args[2] then
local id = ply:LookupPoseParameter(args[1])
if id == -1 then return end
- local n = tonumber(args[2])
- if isnumber(n) then
- SetPoseParameter( ply, id, n )
- broadcast_poseparam(ply, id, n, false)
+
+ if isnumber(tonumber(args[2])) then
+ ply:SetPoseParameter(id, tonumber(args[2]))
+ broadcast_poseparam(ply, id, tonumber(args[2]), false)
+ update_register(ply, "poseparameter", id, tonumber(args[2]))
elseif args[2] == "reset" then
broadcast_poseparam(ply, id, 0, true)
+ update_register(ply, "poseparameter", id, tonumber(args[2]))
end
end
- end, nil, "sends out a request to change your playermodel's pose parameters")
+ end, nil, "sends out a request to change your playermodel's pose parameters.\n\ne.g.\npac_override_poseparameter head_yaw 70\npac_override_poseparameter head_yaw reset\npac_override_poseparameter ^\nusing ^ or reset at the FIRST argument will reset all your poseparameters\nusing reset at the SECOND argument will reset ONE poseparameter")
+ gameevent.Listen( "entity_killed" )
+ hook.Add( "entity_killed", "pac_transfer_submodel_mutations", function( data )
+ if not GetConVar("pac_modifier_model"):GetBool() then return end
+ // Called when a Player or Entity is killed
+ local ent = Entity(data.entindex_killed)
+ if not IsValid(ent) then return end
+ if ent:IsPlayer() then
+ timer.Simple(0.1, function()
+ reapply_modifications(ent:GetRagdollEntity(), ent, true)
+ end)
+ end
+ end)
else
+ CreateClientConVar("pac_override_flexweight_mirrored_on_ragdoll", "0", true, true, "Whether to request that flex weight edits from the pac_override_flexweight command should be networked to re-apply to your corpse ragdoll")
net.Receive("pac_update_poseparameter", function()
local id = net.ReadUInt(5)
local value = net.ReadInt(16) / 100
local reset = net.ReadBool()
local ent = net.ReadEntity()
local name = ent:GetPoseParameterName(id)
- local hookname = "manual_"..name
+ local hook_id = "manual_"..name
ent.pac_pose_params = ent.pac_pose_params or {}
-
- if reset then
- ent.pac_pose_params[hookname] = nil
-
- SetPoseParameter(ent, id, 0)
-
- ent:InvalidateBoneCache()
- else
- ent.pac_pose_params[hookname] = ent.pac_pose_params[hookname] or {}
- ent.pac_pose_params[hookname].key = name
- ent.pac_pose_params[hookname].val = value
-
- SetPoseParameter(ent, id, value)
-
- ent:InvalidateBoneCache()
- end
+ if reset then ent.pac_pose_params[hook_id] = nil end
+ ent.pac_pose_params[hook_id] = ent.pac_pose_params[hook_id] or {}
+ ent.pac_pose_params[hook_id].key = name
+ ent.pac_pose_params[hook_id].val = value
+ ent:SetPoseParameter(id, value)
end)
-end
+ net.Receive("pac_update_flexweight", function()
+ local id = net.ReadUInt(6)
+ local value = net.ReadInt(16) / 100
+ local ent = net.ReadEntity()
+ ent:SetFlexWeight(id, value)
+ if not ent:Alive() then
+ local rag = ent:GetRagdollEntity()
+ if IsValid(rag) then
+ rag:SetFlexWeight(id, value)
+ end
+ end
+ end)
+end
\ No newline at end of file
From fdf401b564540c3b8a67bd2675cb592d2d20c20b Mon Sep 17 00:00:00 2001
From: pingu7867
Date: Fri, 7 Aug 2026 22:00:38 -0400
Subject: [PATCH 05/10] Update model.lua
---
lua/pac3/core/shared/entity_mutators/model.lua | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lua/pac3/core/shared/entity_mutators/model.lua b/lua/pac3/core/shared/entity_mutators/model.lua
index 0f431bc79..5ab367a2e 100644
--- a/lua/pac3/core/shared/entity_mutators/model.lua
+++ b/lua/pac3/core/shared/entity_mutators/model.lua
@@ -273,7 +273,7 @@ if SERVER then
if not ply:IsValid() then return end
if not GetConVar("pac_modifier_model"):GetBool() then return end
local function helptext()
- for i=1,ply:GetFlexNum()-1 do
+ for i=0,ply:GetFlexNum()-1 do
ply:PrintMessage(HUD_PRINTCONSOLE, "[" .. i .. "] " .. ply:GetFlexName(i))
end
end
From 91718fa209d1985759df7f89b467be6e7779a82b Mon Sep 17 00:00:00 2001
From: pingu7867
Date: Sat, 8 Aug 2026 20:40:41 -0400
Subject: [PATCH 06/10] more fixes
revert the setposeparameter function as it remaps wrong. could be temporary or not
---
lua/pac3/core/client/hooks.lua | 2 +-
lua/pac3/core/shared/entity_mutators/model.lua | 15 ++++++++-------
2 files changed, 9 insertions(+), 8 deletions(-)
diff --git a/lua/pac3/core/client/hooks.lua b/lua/pac3/core/client/hooks.lua
index 3c3aa858c..3a4340a07 100644
--- a/lua/pac3/core/client/hooks.lua
+++ b/lua/pac3/core/client/hooks.lua
@@ -47,7 +47,7 @@ do
if tbl then
for i, data in pairs(tbl) do
if ply:LookupPoseParameter(data.key) ~= -1 then
- pac.SetPoseParameter(ply, data.key, data.val)
+ ply:SetPoseParameter(data.key, data.val)
end
end
end
diff --git a/lua/pac3/core/shared/entity_mutators/model.lua b/lua/pac3/core/shared/entity_mutators/model.lua
index 5ab367a2e..5c7141fda 100644
--- a/lua/pac3/core/shared/entity_mutators/model.lua
+++ b/lua/pac3/core/shared/entity_mutators/model.lua
@@ -183,14 +183,14 @@ if SERVER then
--bodygroups are already networked to the ragdoll
if pac.player_submodel_mutations[owner]["bodygroup"] then
for k,v in pairs(pac.player_submodel_mutations[owner]["bodygroup"]) do
- pac.SetPoseParameter(ent, k, v)
+ ent:SetBodygroup(k, v)
end
end
--poseparameters I'm not sure
if pac.player_submodel_mutations[owner]["poseparameter"] then
for k,v in pairs(pac.player_submodel_mutations[owner]["poseparameter"]) do
- pac.SetPoseParameter(ent, k, v)
+ ent:SetPoseParameter(k, v)
end
end
@@ -317,18 +317,19 @@ if SERVER then
if not ply:IsValid() then return end
if not GetConVar("pac_modifier_model"):GetBool() then return end
local function helptext()
- for i=1,ply:GetNumPoseParameters()-1 do
+ for i=0,ply:GetNumPoseParameters()-1 do
local min, max = ply:GetPoseParameterRange(i)
- ply:PrintMessage(HUD_PRINTCONSOLE, "[" .. i .. "] " .. ply:GetPoseParameterName(i) .. " {"..min.."-"..max.."}")
+ ply:PrintMessage(HUD_PRINTCONSOLE, "[" .. i .. "] " .. ply:GetPoseParameterName(i) .. " {"..min..", "..max.."}")
end
end
if not args[1] then
helptext()
elseif args[1] == "^" or args[1] == "reset" then
- for i=0,ply:GetNumPoseParameters()-1 do
+ for id=0,ply:GetNumPoseParameters()-1 do
broadcast_poseparam(ply, id, 0, true)
update_register(ply, "poseparameter", id, nil)
end
+ return
end
if args[1] and args[2] then
local id = ply:LookupPoseParameter(args[1])
@@ -338,7 +339,7 @@ if SERVER then
ply:SetPoseParameter(id, tonumber(args[2]))
broadcast_poseparam(ply, id, tonumber(args[2]), false)
update_register(ply, "poseparameter", id, tonumber(args[2]))
- elseif args[2] == "reset" then
+ elseif args[2] == "^" or args[2] == "reset" then
broadcast_poseparam(ply, id, 0, true)
update_register(ply, "poseparameter", id, tonumber(args[2]))
end
@@ -367,7 +368,7 @@ else
local name = ent:GetPoseParameterName(id)
local hook_id = "manual_"..name
ent.pac_pose_params = ent.pac_pose_params or {}
- if reset then ent.pac_pose_params[hook_id] = nil end
+ if reset then ent.pac_pose_params[hook_id] = nil return end
ent.pac_pose_params[hook_id] = ent.pac_pose_params[hook_id] or {}
ent.pac_pose_params[hook_id].key = name
ent.pac_pose_params[hook_id].val = value
From e8c072b9d0cd0f852873512ae94c8e08ee5eb308 Mon Sep 17 00:00:00 2001
From: pingu7867
Date: Fri, 21 Aug 2026 19:05:54 -0400
Subject: [PATCH 07/10] moved the new code to new file
model_utils.lua
could be expanded later
---
.../core/shared/entity_mutators/model.lua | 320 +----------------
lua/pac3/core/shared/init.lua | 1 +
lua/pac3/core/shared/model_utils.lua | 322 ++++++++++++++++++
3 files changed, 324 insertions(+), 319 deletions(-)
create mode 100644 lua/pac3/core/shared/model_utils.lua
diff --git a/lua/pac3/core/shared/entity_mutators/model.lua b/lua/pac3/core/shared/entity_mutators/model.lua
index 5c7141fda..0fa531979 100644
--- a/lua/pac3/core/shared/entity_mutators/model.lua
+++ b/lua/pac3/core/shared/entity_mutators/model.lua
@@ -68,322 +68,4 @@ function MUTATOR:Mutate(path)
end
end
-pac.emut.Register(MUTATOR)
-
--- Inverse of Lerp()
-local function InvLerp(t, from, to)
- if from==to then return from end
- return (t - from) / (to - from)
-end
-
--- Returns flex bounds, or default [0, 1] as a failsafe
-local function GetFlexBounds(entity, flex)
- local min, max = entity:GetFlexBounds(flex)
- return min or 0, max or 1
-end
-pac.GetFlexBounds = GetFlexBounds
-
--- Convert a flex weight value from [range_min, range_max] into [0, 1]
-local function FromFlexRange(entity, flex, weight)
- return InvLerp( weight, GetFlexBounds(entity, flex) )
-end
-pac.FromFlexRange = FromFlexRange
-
--- Convert a flex weight value from [0, 1] into [range_min, range_max]
-local function ToFlexRange(entity, flex, weight)
- return Lerp( weight, GetFlexBounds(entity, flex) )
-end
-pac.ToFlexRange = ToFlexRange
-
--- Set flex weight, using the flex controller defined range
-local function SetFlexWeight(entity, flex, weight)
- entity:SetFlexWeight(
- flex,
- FromFlexRange( entity, flex, weight )
- )
-end
-pac.SetFlexWeight = SetFlexWeight
-
--- Get flex weight, using the flex controller defined range
-local function GetFlexWeight(entity, flex)
- return ToFlexRange( entity, flex, entity:GetFlexWeight(flex) )
-end
-pac.GetFlexWeight = GetFlexWeight
-
--- Returns pose parameter bounds, or default [0, 1] as a failsafe
-local function GetPoseParameterRange(entity, parameter)
- local min, max = entity:GetPoseParameterRange(parameter)
- return min or 0, max or 1
-end
-pac.GetPoseParameterRange = GetPoseParameterRange
-
-
--- Convert a pose parameter value from [range_min, range_max] into [0, 1]
-local function FromPoseParameterRange(entity, parameter, value)
- return InvLerp( value, GetPoseParameterRange(entity, parameter) )
-end
-pac.FromPoseParameterRange = FromPoseParameterRange
-
--- Convert a pose parameter value from [0, 1] into [range_min, range_max]
-local function ToPoseParameterRange(entity, parameter, value)
- return Lerp( value, GetPoseParameterRange(entity, parameter) )
-end
-pac.ToPoseParameterRange = ToPoseParameterRange
-
--- Set pose parameter, using the pose parameter's defined range
-local function SetPoseParameter(entity, parameter, value)
- entity:SetPoseParameter(
- parameter,
- FromPoseParameterRange( entity, parameter, value )
- )
-end
-pac.SetPoseParameter = SetPoseParameter
-
--- Get pose parameter, using the pose parameter's defined range
-local function GetPoseParameter(entity, parameter)
- return ToPoseParameterRange( entity, parameter, entity:GetPoseParameter(parameter) )
-end
-pac.GetPoseParameter = GetPoseParameter
-
---extras to control other aspects of the model serverside. it's an adjacent functionality to editing your model
---to let simple MDLs work better with pac-disabled and renderdistance cases
-if SERVER then
- pac.player_submodel_mutations = {}
-
- util.AddNetworkString("pac_update_flexweight")
- local function broadcast_flexweight(ply, id, value)
- if ply:GetInfoNum("pac_override_flexweight_mirrored_on_ragdoll", 0) ~= 1 then return end
- if pac.player_submodel_mutations and pac.player_submodel_mutations[ply] then
- if not pac.player_submodel_mutations[ply]["flex"] then return end
- if not pac.player_submodel_mutations[ply]["flex"][id] then return end
- net.Start("pac_update_flexweight", true)
- net.WriteUInt(id, 6)
- net.WriteInt(value * 100, 16)
- net.WriteEntity(ply)
- net.Broadcast()
- end
- end
-
- local function update_register(ply, mutation, key, value)
- pac.player_submodel_mutations[ply] = pac.player_submodel_mutations[ply] or {
- bodygroup = {},
- flex = {},
- poseparameter = {},
- }
- if pac.player_submodel_mutations[ply][mutation] then
- pac.player_submodel_mutations[ply][mutation][key] = value
- end
- end
- local function reapply_modifications(ent, owner, duplicate_to_ragdoll)
-
- if not pac.player_submodel_mutations then return end
- if not pac.player_submodel_mutations[owner] then return end
-
-
- --bodygroups are already networked to the ragdoll
- if pac.player_submodel_mutations[owner]["bodygroup"] then
- for k,v in pairs(pac.player_submodel_mutations[owner]["bodygroup"]) do
- ent:SetBodygroup(k, v)
- end
- end
-
- --poseparameters I'm not sure
- if pac.player_submodel_mutations[owner]["poseparameter"] then
- for k,v in pairs(pac.player_submodel_mutations[owner]["poseparameter"]) do
- ent:SetPoseParameter(k, v)
- end
- end
-
- --flexes need to be reapplied
- if pac.player_submodel_mutations[owner]["flex"] then
- if duplicate_to_ragdoll then
- local limit = 10
- local msg = 0
- local min_value = 0.1
- for k,v in pairs(pac.player_submodel_mutations[owner]["flex"]) do
- if math.abs(v) < min_value then continue end
- if msg > limit then break end
- pac.SetFlexWeight(ent, k, v)
- broadcast_flexweight(owner, k, v)
- msg = msg + 1
- end
- return
- else
- for k,v in pairs(pac.player_submodel_mutations[owner]["flex"]) do
- pac.SetFlexWeight(owner, k, v)
- end
- end
- end
- end
-
- concommand.Add("pac_override_bodygroup", function(ply, name, args, args_str)
- if not ply:IsValid() then return end
- if not GetConVar("pac_modifier_model"):GetBool() then return end
- local function helptext()
- for i,tbl in ipairs(ply:GetBodyGroups()) do
- ply:PrintMessage(HUD_PRINTCONSOLE, "[" .. tbl.id .. "] " .. tbl.name)
- if table.Count(tbl.submodels) > 1 then
- for i2=0, table.Count(tbl.submodels) - 1 do
- local selected = ""
- if i2 == ply:GetBodygroup(tbl.id) then selected = " [active]" end
- ply:PrintMessage(HUD_PRINTCONSOLE, " [" .. i2 .. "] " .. tbl.submodels[i2] .. selected)
- end
- end
- ply:PrintMessage(HUD_PRINTCONSOLE, "\n")
- end
- end
- if not args[1] then
- helptext()
- elseif args[1] == "^" or args[1] == "reset" then
- for i, str in ipairs(string.Split(ply:GetInfo("cl_playerbodygroups")," ")) do
- ply:SetBodygroup(i-1, tonumber(str))
- end
- end
- if args[1] and args[2] then
- local id = ply:FindBodygroupByName(args[1])
- if id == -1 then ply:PrintMessage(HUD_PRINTCONSOLE, "invalid bodygroup!") helptext() return end
-
- if args[2] == "+" then
- local val = (ply:GetBodygroup(id)+1) % (ply:GetBodygroupCount(id))
- ply:SetBodygroup(id,val)
- update_register(ply, "bodygroup", id, val)
- elseif args[2] == "-" then
- local val = (ply:GetBodygroup(id)-1) % (ply:GetBodygroupCount(id))
- ply:SetBodygroup(id,val)
- update_register(ply, "bodygroup", id, val)
- elseif args[2] == "toggle" then
- if ply:GetBodygroup(id) >= 1 then
- ply:SetBodygroup(id, 0)
- update_register(ply, "bodygroup", id, 0)
- else
- ply:SetBodygroup(id, 1)
- update_register(ply, "bodygroup", id, 1)
- end
- elseif isnumber(tonumber(args[2])) then
- ply:SetBodygroup(id, -1)
- ply:SetBodygroup(id, tonumber(args[2]))
- update_register(ply, "bodygroup", id, tonumber(args[2]))
- end
- elseif args[1] then
- ply:SetBodyGroups(args[1])
- end
- end, nil, "sends out a request to change your playermodel's bodygroups, but you'll need to stop your entity parts from changing bodygroups\n\ne.g.\npac_override_bodygroup 1pac_override_bodygroup 'Head Dress' 1")
-
- concommand.Add("pac_override_flexweight", function(ply, name, args, args_str)
- if not ply:IsValid() then return end
- if not GetConVar("pac_modifier_model"):GetBool() then return end
- local function helptext()
- for i=0,ply:GetFlexNum()-1 do
- ply:PrintMessage(HUD_PRINTCONSOLE, "[" .. i .. "] " .. ply:GetFlexName(i))
- end
- end
- if not args[1] then
- helptext()
- elseif args[1] == "^" or args[1] == "reset" then
- for i=0,ply:GetFlexNum()-1 do
- pac.SetFlexWeight(ply, i, 0)
- update_register(ply, "flex", i, nil)
- end
- end
- if args[1] and args[2] then
- local id = ply:GetFlexIDByName(args[1]) or tonumber(args[1])
- if id == nil then return end
-
- if args[2] == "toggle" then
- local val = ply:GetFlexWeight(id) < 0.5 and 1 or 0
- pac.SetFlexWeight(ply, id, -1)
- pac.SetFlexWeight(ply, id, val)
- update_register(ply, "flex", id, val)
- broadcast_flexweight(ply, id, val)
- elseif isnumber(tonumber(args[2])) then
- pac.SetFlexWeight(ply, id, tonumber(args[2]))
- update_register(ply, "flex", id, tonumber(args[2]))
- broadcast_flexweight(ply, id, tonumber(args[2]))
- end
- end
- end, nil, "sends out a request to change your playermodel's flex weights\n\ne.g.\npac_override_flexweight blink-happy 1\npac_override_flexweight ^\npac_override_flexweight blink toggle\n\nthe toggle mode switches between 0 and 1, depending on whether the serverside value is above 0.5")
-
- util.AddNetworkString("pac_update_poseparameter")
- local function broadcast_poseparam(ply, id, value, reset)
- net.Start("pac_update_poseparameter", true)
- net.WriteUInt(id, 5)
- net.WriteInt(value * 100, 16)
- net.WriteBool(reset)
- net.WriteEntity(ply)
- net.Broadcast()
- end
-
- concommand.Add("pac_override_poseparameter", function(ply, name, args, args_str)
- if not ply:IsValid() then return end
- if not GetConVar("pac_modifier_model"):GetBool() then return end
- local function helptext()
- for i=0,ply:GetNumPoseParameters()-1 do
- local min, max = ply:GetPoseParameterRange(i)
- ply:PrintMessage(HUD_PRINTCONSOLE, "[" .. i .. "] " .. ply:GetPoseParameterName(i) .. " {"..min..", "..max.."}")
- end
- end
- if not args[1] then
- helptext()
- elseif args[1] == "^" or args[1] == "reset" then
- for id=0,ply:GetNumPoseParameters()-1 do
- broadcast_poseparam(ply, id, 0, true)
- update_register(ply, "poseparameter", id, nil)
- end
- return
- end
- if args[1] and args[2] then
- local id = ply:LookupPoseParameter(args[1])
- if id == -1 then return end
-
- if isnumber(tonumber(args[2])) then
- ply:SetPoseParameter(id, tonumber(args[2]))
- broadcast_poseparam(ply, id, tonumber(args[2]), false)
- update_register(ply, "poseparameter", id, tonumber(args[2]))
- elseif args[2] == "^" or args[2] == "reset" then
- broadcast_poseparam(ply, id, 0, true)
- update_register(ply, "poseparameter", id, tonumber(args[2]))
- end
- end
- end, nil, "sends out a request to change your playermodel's pose parameters.\n\ne.g.\npac_override_poseparameter head_yaw 70\npac_override_poseparameter head_yaw reset\npac_override_poseparameter ^\nusing ^ or reset at the FIRST argument will reset all your poseparameters\nusing reset at the SECOND argument will reset ONE poseparameter")
-
- gameevent.Listen( "entity_killed" )
- hook.Add( "entity_killed", "pac_transfer_submodel_mutations", function( data )
- if not GetConVar("pac_modifier_model"):GetBool() then return end
- // Called when a Player or Entity is killed
- local ent = Entity(data.entindex_killed)
- if not IsValid(ent) then return end
- if ent:IsPlayer() then
- timer.Simple(0.1, function()
- reapply_modifications(ent:GetRagdollEntity(), ent, true)
- end)
- end
- end)
-else
- CreateClientConVar("pac_override_flexweight_mirrored_on_ragdoll", "0", true, true, "Whether to request that flex weight edits from the pac_override_flexweight command should be networked to re-apply to your corpse ragdoll")
- net.Receive("pac_update_poseparameter", function()
- local id = net.ReadUInt(5)
- local value = net.ReadInt(16) / 100
- local reset = net.ReadBool()
- local ent = net.ReadEntity()
- local name = ent:GetPoseParameterName(id)
- local hook_id = "manual_"..name
- ent.pac_pose_params = ent.pac_pose_params or {}
- if reset then ent.pac_pose_params[hook_id] = nil return end
- ent.pac_pose_params[hook_id] = ent.pac_pose_params[hook_id] or {}
- ent.pac_pose_params[hook_id].key = name
- ent.pac_pose_params[hook_id].val = value
- ent:SetPoseParameter(id, value)
- end)
- net.Receive("pac_update_flexweight", function()
- local id = net.ReadUInt(6)
- local value = net.ReadInt(16) / 100
- local ent = net.ReadEntity()
- ent:SetFlexWeight(id, value)
- if not ent:Alive() then
- local rag = ent:GetRagdollEntity()
- if IsValid(rag) then
- rag:SetFlexWeight(id, value)
- end
- end
- end)
-end
\ No newline at end of file
+pac.emut.Register(MUTATOR)
\ No newline at end of file
diff --git a/lua/pac3/core/shared/init.lua b/lua/pac3/core/shared/init.lua
index 382905062..d7b8a4510 100644
--- a/lua/pac3/core/shared/init.lua
+++ b/lua/pac3/core/shared/init.lua
@@ -5,6 +5,7 @@ include("http.lua")
include("movement.lua")
include("entity_mutator.lua")
include("hash.lua")
+include("model_utils.lua")
pac.StringStream = include("pac3/libraries/string_stream.lua")
diff --git a/lua/pac3/core/shared/model_utils.lua b/lua/pac3/core/shared/model_utils.lua
new file mode 100644
index 000000000..661a9ad20
--- /dev/null
+++ b/lua/pac3/core/shared/model_utils.lua
@@ -0,0 +1,322 @@
+--code moved from entity mutators
+--extras to control other aspects of the model serverside. it's an adjacent functionality to editing your model
+--without the need for active parts
+--to let simple MDLs work better with pac-disabled and renderdistance cases
+
+
+-- Inverse of Lerp()
+local function InvLerp(t, from, to)
+ if from==to then return from end
+ return (t - from) / (to - from)
+end
+
+-- Returns flex bounds, or default [0, 1] as a failsafe
+local function GetFlexBounds(entity, flex)
+ local min, max = entity:GetFlexBounds(flex)
+ return min or 0, max or 1
+end
+pac.GetFlexBounds = GetFlexBounds
+
+-- Convert a flex weight value from [range_min, range_max] into [0, 1]
+local function FromFlexRange(entity, flex, weight)
+ return InvLerp( weight, GetFlexBounds(entity, flex) )
+end
+pac.FromFlexRange = FromFlexRange
+
+-- Convert a flex weight value from [0, 1] into [range_min, range_max]
+local function ToFlexRange(entity, flex, weight)
+ return Lerp( weight, GetFlexBounds(entity, flex) )
+end
+pac.ToFlexRange = ToFlexRange
+
+-- Set flex weight, using the flex controller defined range
+local function SetFlexWeight(entity, flex, weight)
+ entity:SetFlexWeight(
+ flex,
+ FromFlexRange( entity, flex, weight )
+ )
+end
+pac.SetFlexWeight = SetFlexWeight
+
+-- Get flex weight, using the flex controller defined range
+local function GetFlexWeight(entity, flex)
+ return ToFlexRange( entity, flex, entity:GetFlexWeight(flex) )
+end
+pac.GetFlexWeight = GetFlexWeight
+
+-- Returns pose parameter bounds, or default [0, 1] as a failsafe
+local function GetPoseParameterRange(entity, parameter)
+ local min, max = entity:GetPoseParameterRange(parameter)
+ return min or 0, max or 1
+end
+pac.GetPoseParameterRange = GetPoseParameterRange
+
+
+-- Convert a pose parameter value from [range_min, range_max] into [0, 1]
+local function FromPoseParameterRange(entity, parameter, value)
+ return InvLerp( value, GetPoseParameterRange(entity, parameter) )
+end
+pac.FromPoseParameterRange = FromPoseParameterRange
+
+-- Convert a pose parameter value from [0, 1] into [range_min, range_max]
+local function ToPoseParameterRange(entity, parameter, value)
+ return Lerp( value, GetPoseParameterRange(entity, parameter) )
+end
+pac.ToPoseParameterRange = ToPoseParameterRange
+
+-- Set pose parameter, using the pose parameter's defined range
+local function SetPoseParameter(entity, parameter, value)
+ entity:SetPoseParameter(
+ parameter,
+ FromPoseParameterRange( entity, parameter, value )
+ )
+end
+pac.SetPoseParameter = SetPoseParameter
+
+-- Get pose parameter, using the pose parameter's defined range
+local function GetPoseParameter(entity, parameter)
+ return ToPoseParameterRange( entity, parameter, entity:GetPoseParameter(parameter) )
+end
+pac.GetPoseParameter = GetPoseParameter
+
+if SERVER then
+ pac.player_submodel_mutations = {}
+
+ util.AddNetworkString("pac_update_flexweight")
+ local function broadcast_flexweight(ply, id, value)
+ if ply:GetInfoNum("pac_override_flexweight_mirrored_on_ragdoll", 0) ~= 1 then return end
+ if pac.player_submodel_mutations and pac.player_submodel_mutations[ply] then
+ if not pac.player_submodel_mutations[ply]["flex"] then return end
+ if not pac.player_submodel_mutations[ply]["flex"][id] then return end
+ net.Start("pac_update_flexweight", true)
+ net.WriteUInt(id, 6)
+ net.WriteInt(value * 100, 16)
+ net.WriteEntity(ply)
+ net.Broadcast()
+ end
+ end
+
+ local function update_register(ply, mutation, key, value)
+ pac.player_submodel_mutations[ply] = pac.player_submodel_mutations[ply] or {
+ bodygroup = {},
+ flex = {},
+ poseparameter = {},
+ }
+ if pac.player_submodel_mutations[ply][mutation] then
+ pac.player_submodel_mutations[ply][mutation][key] = value
+ end
+ end
+ local function reapply_modifications(ent, owner, duplicate_to_ragdoll)
+
+ if not pac.player_submodel_mutations then return end
+ if not pac.player_submodel_mutations[owner] then return end
+
+
+ --bodygroups are already networked to the ragdoll
+ if pac.player_submodel_mutations[owner]["bodygroup"] then
+ for k,v in pairs(pac.player_submodel_mutations[owner]["bodygroup"]) do
+ ent:SetBodygroup(k, v)
+ end
+ end
+
+ --poseparameters I'm not sure
+ if pac.player_submodel_mutations[owner]["poseparameter"] then
+ for k,v in pairs(pac.player_submodel_mutations[owner]["poseparameter"]) do
+ ent:SetPoseParameter(k, v)
+ end
+ end
+
+ --flexes need to be reapplied
+ if pac.player_submodel_mutations[owner]["flex"] then
+ if duplicate_to_ragdoll then
+ local limit = 10
+ local msg = 0
+ local min_value = 0.1
+ for k,v in pairs(pac.player_submodel_mutations[owner]["flex"]) do
+ if math.abs(v) < min_value then continue end
+ if msg > limit then break end
+ pac.SetFlexWeight(ent, k, v)
+ broadcast_flexweight(owner, k, v)
+ msg = msg + 1
+ end
+ return
+ else
+ for k,v in pairs(pac.player_submodel_mutations[owner]["flex"]) do
+ pac.SetFlexWeight(owner, k, v)
+ end
+ end
+ end
+ end
+
+ concommand.Add("pac_override_bodygroup", function(ply, name, args, args_str)
+ if not ply:IsValid() then return end
+ if not GetConVar("pac_modifier_model"):GetBool() then return end
+ local function helptext()
+ for i,tbl in ipairs(ply:GetBodyGroups()) do
+ ply:PrintMessage(HUD_PRINTCONSOLE, "[" .. tbl.id .. "] " .. tbl.name)
+ if table.Count(tbl.submodels) > 1 then
+ for i2=0, table.Count(tbl.submodels) - 1 do
+ local selected = ""
+ if i2 == ply:GetBodygroup(tbl.id) then selected = " [active]" end
+ ply:PrintMessage(HUD_PRINTCONSOLE, " [" .. i2 .. "] " .. tbl.submodels[i2] .. selected)
+ end
+ end
+ ply:PrintMessage(HUD_PRINTCONSOLE, "\n")
+ end
+ end
+ if not args[1] then
+ helptext()
+ elseif args[1] == "^" or args[1] == "reset" then
+ for i, str in ipairs(string.Split(ply:GetInfo("cl_playerbodygroups")," ")) do
+ ply:SetBodygroup(i-1, tonumber(str))
+ end
+ end
+ if args[1] and args[2] then
+ local id = ply:FindBodygroupByName(args[1])
+ if id == -1 then ply:PrintMessage(HUD_PRINTCONSOLE, "invalid bodygroup!") helptext() return end
+
+ if args[2] == "+" then
+ local val = (ply:GetBodygroup(id)+1) % (ply:GetBodygroupCount(id))
+ ply:SetBodygroup(id,val)
+ update_register(ply, "bodygroup", id, val)
+ elseif args[2] == "-" then
+ local val = (ply:GetBodygroup(id)-1) % (ply:GetBodygroupCount(id))
+ ply:SetBodygroup(id,val)
+ update_register(ply, "bodygroup", id, val)
+ elseif args[2] == "toggle" then
+ if ply:GetBodygroup(id) >= 1 then
+ ply:SetBodygroup(id, 0)
+ update_register(ply, "bodygroup", id, 0)
+ else
+ ply:SetBodygroup(id, 1)
+ update_register(ply, "bodygroup", id, 1)
+ end
+ elseif isnumber(tonumber(args[2])) then
+ ply:SetBodygroup(id, -1)
+ ply:SetBodygroup(id, tonumber(args[2]))
+ update_register(ply, "bodygroup", id, tonumber(args[2]))
+ end
+ elseif args[1] then
+ ply:SetBodyGroups(args[1])
+ end
+ end, nil, "sends out a request to change your playermodel's bodygroups, but you'll need to stop your entity parts from changing bodygroups\n\ne.g.\npac_override_bodygroup 1pac_override_bodygroup 'Head Dress' 1")
+
+ concommand.Add("pac_override_flexweight", function(ply, name, args, args_str)
+ if not ply:IsValid() then return end
+ if not GetConVar("pac_modifier_model"):GetBool() then return end
+ local function helptext()
+ for i=0,ply:GetFlexNum()-1 do
+ ply:PrintMessage(HUD_PRINTCONSOLE, "[" .. i .. "] " .. ply:GetFlexName(i))
+ end
+ end
+ if not args[1] then
+ helptext()
+ elseif args[1] == "^" or args[1] == "reset" then
+ for i=0,ply:GetFlexNum()-1 do
+ pac.SetFlexWeight(ply, i, 0)
+ update_register(ply, "flex", i, nil)
+ end
+ end
+ if args[1] and args[2] then
+ local id = ply:GetFlexIDByName(args[1]) or tonumber(args[1])
+ if id == nil then return end
+
+ if args[2] == "toggle" then
+ local val = ply:GetFlexWeight(id) < 0.5 and 1 or 0
+ pac.SetFlexWeight(ply, id, -1)
+ pac.SetFlexWeight(ply, id, val)
+ update_register(ply, "flex", id, val)
+ broadcast_flexweight(ply, id, val)
+ elseif isnumber(tonumber(args[2])) then
+ pac.SetFlexWeight(ply, id, tonumber(args[2]))
+ update_register(ply, "flex", id, tonumber(args[2]))
+ broadcast_flexweight(ply, id, tonumber(args[2]))
+ end
+ end
+ end, nil, "sends out a request to change your playermodel's flex weights\n\ne.g.\npac_override_flexweight blink-happy 1\npac_override_flexweight ^\npac_override_flexweight blink toggle\n\nthe toggle mode switches between 0 and 1, depending on whether the serverside value is above 0.5")
+
+ util.AddNetworkString("pac_update_poseparameter")
+ local function broadcast_poseparam(ply, id, value, reset)
+ net.Start("pac_update_poseparameter", true)
+ net.WriteUInt(id, 5)
+ net.WriteInt(value * 100, 16)
+ net.WriteBool(reset)
+ net.WriteEntity(ply)
+ net.Broadcast()
+ end
+
+ concommand.Add("pac_override_poseparameter", function(ply, name, args, args_str)
+ if not ply:IsValid() then return end
+ if not GetConVar("pac_modifier_model"):GetBool() then return end
+ local function helptext()
+ for i=0,ply:GetNumPoseParameters()-1 do
+ local min, max = ply:GetPoseParameterRange(i)
+ ply:PrintMessage(HUD_PRINTCONSOLE, "[" .. i .. "] " .. ply:GetPoseParameterName(i) .. " {"..min..", "..max.."}")
+ end
+ end
+ if not args[1] then
+ helptext()
+ elseif args[1] == "^" or args[1] == "reset" then
+ for id=0,ply:GetNumPoseParameters()-1 do
+ broadcast_poseparam(ply, id, 0, true)
+ update_register(ply, "poseparameter", id, nil)
+ end
+ return
+ end
+ if args[1] and args[2] then
+ local id = ply:LookupPoseParameter(args[1])
+ if id == -1 then return end
+
+ if isnumber(tonumber(args[2])) then
+ ply:SetPoseParameter(id, tonumber(args[2]))
+ broadcast_poseparam(ply, id, tonumber(args[2]), false)
+ update_register(ply, "poseparameter", id, tonumber(args[2]))
+ elseif args[2] == "^" or args[2] == "reset" then
+ broadcast_poseparam(ply, id, 0, true)
+ update_register(ply, "poseparameter", id, tonumber(args[2]))
+ end
+ end
+ end, nil, "sends out a request to change your playermodel's pose parameters.\n\ne.g.\npac_override_poseparameter head_yaw 70\npac_override_poseparameter head_yaw reset\npac_override_poseparameter ^\nusing ^ or reset at the FIRST argument will reset all your poseparameters\nusing reset at the SECOND argument will reset ONE poseparameter")
+
+ gameevent.Listen( "entity_killed" )
+ hook.Add( "entity_killed", "pac_transfer_submodel_mutations", function( data )
+ if not GetConVar("pac_modifier_model"):GetBool() then return end
+ // Called when a Player or Entity is killed
+ local ent = Entity(data.entindex_killed)
+ if not IsValid(ent) then return end
+ if ent:IsPlayer() then
+ timer.Simple(0.1, function()
+ reapply_modifications(ent:GetRagdollEntity(), ent, true)
+ end)
+ end
+ end)
+else
+ CreateClientConVar("pac_override_flexweight_mirrored_on_ragdoll", "0", true, true, "Whether to request that flex weight edits from the pac_override_flexweight command should be networked to re-apply to your corpse ragdoll")
+ net.Receive("pac_update_poseparameter", function()
+ local id = net.ReadUInt(5)
+ local value = net.ReadInt(16) / 100
+ local reset = net.ReadBool()
+ local ent = net.ReadEntity()
+ local name = ent:GetPoseParameterName(id)
+ local hook_id = "manual_"..name
+ ent.pac_pose_params = ent.pac_pose_params or {}
+ if reset then ent.pac_pose_params[hook_id] = nil return end
+ ent.pac_pose_params[hook_id] = ent.pac_pose_params[hook_id] or {}
+ ent.pac_pose_params[hook_id].key = name
+ ent.pac_pose_params[hook_id].val = value
+ ent:SetPoseParameter(id, value)
+ end)
+ net.Receive("pac_update_flexweight", function()
+ local id = net.ReadUInt(6)
+ local value = net.ReadInt(16) / 100
+ local ent = net.ReadEntity()
+
+ ent:SetFlexWeight(id, value)
+ if not ent:Alive() then
+ local rag = ent:GetRagdollEntity()
+ if IsValid(rag) then
+ rag:SetFlexWeight(id, value)
+ end
+ end
+ end)
+end
From ccc40f2bdd92d16f4bd2203982b1205b31dfb38c Mon Sep 17 00:00:00 2001
From: pingu7867
Date: Fri, 21 Aug 2026 21:15:01 -0400
Subject: [PATCH 08/10] revert / minimalize changes
---
lua/pac3/core/client/parts/poseparameter.lua | 15 +++------------
1 file changed, 3 insertions(+), 12 deletions(-)
diff --git a/lua/pac3/core/client/parts/poseparameter.lua b/lua/pac3/core/client/parts/poseparameter.lua
index f8c2ebf4e..bad67ac5a 100644
--- a/lua/pac3/core/client/parts/poseparameter.lua
+++ b/lua/pac3/core/client/parts/poseparameter.lua
@@ -9,7 +9,7 @@ PART.Icon = 'icon16/disconnect.png'
BUILDER:StartStorableVars()
BUILDER:GetSet("PoseParameter", "", {enums = function(part) return part:GetPoseParameterList() end})
BUILDER:GetSet("Range", 0)
- BUILDER:GetSet("UseRange", false, {description="Limits the output range of the Pose Parameter to be within the legal ranges defined by the model"})
+ BUILDER:GetSet("RawRange", false, {description="Passes the raw value of Range to SetPoseParameter instead of remapping.\nFor example with head_yaw, a range of [-1,1] would then need to use [-75,75]"})
BUILDER:EndStorableVars()
function PART:GetNiceName()
@@ -52,19 +52,10 @@ function PART:UpdateParams()
if data then
local num
- if self.UseRange then
+ if self.RawRange then
num = self.Range
else
- -- backwards compatibility; reverts the math in the new setter
- -- old calculation
-
num = Lerp((self.Range + 1) / 2, data.range[1] or 0, data.range[2] or 1)
-
- num = pac.ToPoseParameterRange(
- ent,
- data.name,
- num
- )
end
ent.pac_pose_params = ent.pac_pose_params or {}
@@ -73,7 +64,7 @@ function PART:UpdateParams()
ent.pac_pose_params[self.UniqueID].key = data.name
ent.pac_pose_params[self.UniqueID].val = num
- pac.SetPoseParameter(ent, data.name, num)
+ ent:SetPoseParameter(data.name, num)
end
end
end
From a719adbd0c63c063dcc68140402c28b875affc44 Mon Sep 17 00:00:00 2001
From: pingu7867
Date: Fri, 21 Aug 2026 21:15:26 -0400
Subject: [PATCH 09/10] Revert "Updated expressions.lua with the correct file"
This reverts commit e9c2f242032ccd08dbd17d325a57293dc724aa04.
---
lua/pac3/libraries/expression.lua | 35 +++++++------------------------
1 file changed, 8 insertions(+), 27 deletions(-)
diff --git a/lua/pac3/libraries/expression.lua b/lua/pac3/libraries/expression.lua
index 216fd137d..328bcbbc4 100644
--- a/lua/pac3/libraries/expression.lua
+++ b/lua/pac3/libraries/expression.lua
@@ -1,3 +1,4 @@
+
local lib = {
PI = math.pi,
rand = math.random,
@@ -43,14 +44,6 @@ local blacklist = {
"repeat"; "until";
}
--- convert blacklist items into patterns to match
-for k, item in pairs(blacklist) do
- -- uses more efficient / conclusive frontier pattern syntax
- -- frontier matches transition into and out of sets
- -- in this case, matching transition into letters and then out of letters (to match whole words and not partials)
- blacklist[k] = ("%%f[%%a](%s)%%f[%%A]"):format(item)
-end
-
local function_intro = "local IN = (...); "
local function_formats = {
@@ -70,6 +63,8 @@ local function CompileStringAdvanced(code, identifier)
for _, structure in pairs(function_formats) do
success, func = TryCompile(structure:format(code), identifier)
+ print(structure, code)
+
if success then break end
end
@@ -77,7 +72,7 @@ local function CompileStringAdvanced(code, identifier)
end
local function readonlyError()
- error("Not allowed to assign to globals", 2)
+ error("Not allowed to assign to globals", 3)
end
local function makeReadonly(t)
@@ -95,26 +90,12 @@ local function copyInto(t1, t2)
for k,v in pairs(t1) do t2[k] = v end
end
-local function checkBlacklist(code)
- local str
-
- for _, word in pairs(blacklist) do
- str = code:match(word)
-
- if str then
- return str
+local function compile_expression(str, extra_lib)
+ for _, word in pairs(blacklist) do
+ if str:find("[%p%s]" .. word) or str:find(word .. "[%p%s]") then
+ return false, string.format("illegal characters used %q", word)
end
end
-
- return nil
-end
-
-local function compile_expression(str, extra_lib)
- local illegalWord = checkBlacklist(str)
-
- if illegalWord then
- return false, string.format("illegal characters used %q", illegalWord)
- end
local success, func = CompileStringAdvanced(str, "pac_expression")
From 242f76602c29409d2d9111c17342aa03a0fdc010 Mon Sep 17 00:00:00 2001
From: pingu7867
Date: Fri, 21 Aug 2026 21:15:32 -0400
Subject: [PATCH 10/10] Revert "Expanded Proxy Expressions"
This reverts commit 4445d75269d2806cf77ae51a39de6b8cfba56a08.
---
lua/pac3/libraries/expression.lua | 82 ++++++-------------------------
1 file changed, 14 insertions(+), 68 deletions(-)
diff --git a/lua/pac3/libraries/expression.lua b/lua/pac3/libraries/expression.lua
index 328bcbbc4..f44ad9655 100644
--- a/lua/pac3/libraries/expression.lua
+++ b/lua/pac3/libraries/expression.lua
@@ -37,58 +37,7 @@ local lib = {
round = math.Round,
}
-local blacklist = {
- "function";
- "for"; "break";
- "while"; "do";
- "repeat"; "until";
-}
-
-local function_intro = "local IN = (...); "
-
-local function_formats = {
- function_intro .. "return %s";
- function_intro .. "%s"; -- allows return semantics
-}
-
-local function TryCompile(code, identifier)
- local result = CompileString(code, identifier, false)
-
- return not isstring(result), result
-end
-
-local function CompileStringAdvanced(code, identifier)
- local success, func = false, nil
-
- for _, structure in pairs(function_formats) do
- success, func = TryCompile(structure:format(code), identifier)
-
- print(structure, code)
-
- if success then break end
- end
-
- return success, func
-end
-
-local function readonlyError()
- error("Not allowed to assign to globals", 3)
-end
-
-local function makeReadonly(t)
- return setmetatable(
- {},
- {
- __index = t,
- __newindex = readonlyError,
- __metatable = "This metatable is locked."
- }
- )
-end
-
-local function copyInto(t1, t2)
- for k,v in pairs(t1) do t2[k] = v end
-end
+local blacklist = {"repeat", "until", "function", "end"}
local function compile_expression(str, extra_lib)
for _, word in pairs(blacklist) do
@@ -97,28 +46,25 @@ local function compile_expression(str, extra_lib)
end
end
- local success, func = CompileStringAdvanced(str, "pac_expression")
+ local functions = {}
- if success then
- local functions = {}
+ for k,v in pairs(lib) do functions[k] = v end
+
+ if extra_lib then
+ for k,v in pairs(extra_lib) do functions[k] = v end
+ end
- copyInto(lib, functions)
+ functions.select = select
+ str = "local IN = select(1, ...) return " .. str
- if extra_lib then
- copyInto(extra_lib, functions)
- end
+ local func = CompileString(str, "pac_expression", false)
- functions.select = select
-
- setfenv(
- func,
- makeReadonly(functions)
- )
-
+ if isstring(func) then
+ return false, func
+ else
+ setfenv(func, functions)
return true, func
end
-
- return false, func
end
return compile_expression