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..3a4340a07 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 + ply:SetPoseParameter(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..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 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 3a3b5de19..2d81650a1 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("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) :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..bad67ac5a 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("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() @@ -49,7 +50,13 @@ 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.RawRange then + num = self.Range + else + num = Lerp((self.Range + 1) / 2, data.range[1] or 0, data.range[2] or 1) + end ent.pac_pose_params = ent.pac_pose_params or {} ent.pac_pose_params[self.UniqueID] = ent.pac_pose_params[self.UniqueID] or {} @@ -62,6 +69,10 @@ function PART:UpdateParams() end end +function PART:OnBuildBonePositions() + self:UpdateParams() +end + function PART:OnHide() local ent = self:GetOwner() 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