Files
wnsrc/gamemodes/sandbox/entities/weapons/gmod_tool/stools/faceposer.lua
lifestorm ba1fc01b16 Upload
2024-08-04 23:12:27 +03:00

464 lines
13 KiB
Lua

--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
TOOL.Category = "Poser"
TOOL.Name = "#tool.faceposer.name"
local MAXSTUDIOFLEXCTRL = 96
TOOL.FaceTimer = 0
TOOL.Information = {
{ name = "left" },
{ name = "right" }
}
local function IsUselessFaceFlex( strName )
if ( strName == "gesture_rightleft" ) then return true end
if ( strName == "gesture_updown" ) then return true end
if ( strName == "head_forwardback" ) then return true end
if ( strName == "chest_rightleft" ) then return true end
if ( strName == "body_rightleft" ) then return true end
if ( strName == "eyes_rightleft" ) then return true end
if ( strName == "eyes_updown" ) then return true end
if ( strName == "head_tilt" ) then return true end
if ( strName == "head_updown" ) then return true end
if ( strName == "head_rightleft" ) then return true end
return false
end
local function GenerateDefaultFlexValue( ent, flexID )
local min, max = ent:GetFlexBounds( flexID )
if ( !max || max - min == 0 ) then return 0 end
return ( 0 - min ) / ( max - min )
end
function TOOL:FacePoserEntity()
return self:GetWeapon():GetNWEntity( 1 )
end
function TOOL:SetFacePoserEntity( ent )
if ( IsValid( ent ) && ent:GetClass() == "prop_effect" ) then ent = ent.AttachedEntity end
return self:GetWeapon():SetNWEntity( 1, ent )
end
local LastFPEntity = NULL
local LastFPEntityValid = false
function TOOL:Think()
-- If we're on the client just make sure the context menu is up to date
if ( CLIENT ) then
if ( self:FacePoserEntity() == LastFPEntity && IsValid( LastFPEntity ) == LastFPEntityValid ) then return end
LastFPEntity = self:FacePoserEntity()
LastFPEntityValid = IsValid( LastFPEntity )
self:RebuildControlPanel( self:FacePoserEntity() )
return
end
-- On the server we continually set the flex weights
if ( self.FaceTimer > CurTime() ) then return end
local ent = self:FacePoserEntity()
if ( !IsValid( ent ) ) then return end
local FlexNum = ent:GetFlexNum()
if ( FlexNum <= 0 ) then return end
for i = 0, FlexNum do
local num = self:GetClientNumber( "flex" .. i )
ent:SetFlexWeight( i, num )
end
local num = self:GetClientNumber( "scale" )
ent:SetFlexScale( num )
end
--[[---------------------------------------------------------
Alt fire sucks the facepose from the model's face
-----------------------------------------------------------]]
function TOOL:RightClick( trace )
local ent = trace.Entity
if ( IsValid( ent ) && ent:GetClass() == "prop_effect" ) then ent = ent.AttachedEntity end
if ( SERVER ) then
self:SetFacePoserEntity( ent )
end
if ( !IsValid( ent ) ) then return true end
local FlexNum = ent:GetFlexNum()
if ( FlexNum == 0 ) then return false end
if ( SERVER ) then
-- This stops it applying the current sliders to the newly selected face..
-- it should probably be linked to the ping somehow.. but 1 second seems pretty safe
self.FaceTimer = CurTime() + 1
-- In multiplayer the rest is only done on the client to save bandwidth.
-- We can't do that in single player because these functions don't get called on the client
if ( !game.SinglePlayer() ) then return true end
end
for i = 0, FlexNum - 1 do
local Weight = "0.0"
if ( !ent:HasFlexManipulatior() ) then
Weight = GenerateDefaultFlexValue( ent, i )
elseif ( i <= FlexNum ) then
Weight = ent:GetFlexWeight( i )
end
self:GetOwner():ConCommand( "faceposer_flex" .. i .. " " .. Weight )
end
self:GetOwner():ConCommand( "faceposer_scale " .. ent:GetFlexScale() )
return true
end
--[[---------------------------------------------------------
Just select as the current object
Current settings will get applied
-----------------------------------------------------------]]
function TOOL:LeftClick( trace )
local ent = trace.Entity
if ( IsValid( ent ) && ent:GetClass() == "prop_effect" ) then ent = ent.AttachedEntity end
if ( !IsValid( ent ) ) then return false end
if ( ent:GetFlexNum() == 0 ) then return false end
self.FaceTimer = 0
self:SetFacePoserEntity( ent )
return true
end
if ( SERVER ) then
local function CC_Face_Randomize( ply, command, arguments )
for i = 0, MAXSTUDIOFLEXCTRL do
local num = math.Rand( 0, 1 )
ply:ConCommand( "faceposer_flex" .. i .. " " .. string.format( "%.3f", num ) )
end
end
concommand.Add( "faceposer_randomize", CC_Face_Randomize )
end
-- The rest of the code is clientside only, it is not used on server
if ( SERVER ) then return end
for i = 0, MAXSTUDIOFLEXCTRL do
TOOL.ClientConVar[ "flex" .. i ] = "0"
end
TOOL.ClientConVar[ "scale" ] = "1.0"
local ConVarsDefault = TOOL:BuildConVarList()
function TOOL.BuildCPanel( CPanel, faceEntity )
CPanel:AddControl( "Header", { Description = "#tool.faceposer.desc" } )
if ( !IsValid( faceEntity ) || faceEntity:GetFlexNum() == 0 ) then return end
CPanel:AddControl( "ComboBox", { MenuButton = 1, Folder = "face", Options = { [ "#preset.default" ] = ConVarsDefault }, CVars = table.GetKeys( ConVarsDefault ) } )
local QuickFace = vgui.Create( "MatSelect", CPanel )
QuickFace:SetItemWidth( 64 )
QuickFace:SetItemHeight( 32 )
QuickFace.List:SetSpacing( 1 )
QuickFace.List:SetPadding( 0 )
QuickFace:SetAutoHeight( true )
local Clear = {}
for i = 0, MAXSTUDIOFLEXCTRL do
Clear[ "faceposer_flex" .. i ] = GenerateDefaultFlexValue( faceEntity, i )
end
QuickFace:AddMaterialEx( "#faceposer.clear", "vgui/face/clear", nil, Clear )
-- Todo: These really need to be the name of the flex.
QuickFace:AddMaterialEx( "#faceposer.openeyes", "vgui/face/open_eyes", nil, {
faceposer_flex0 = "1",
faceposer_flex1 = "1",
faceposer_flex2 = "0",
faceposer_flex3 = "0",
faceposer_flex4 = "0",
faceposer_flex5 = "0",
faceposer_flex6 = "0",
faceposer_flex7 = "0",
faceposer_flex8 = "0",
faceposer_flex9 = "0"
} )
QuickFace:AddMaterialEx( "#faceposer.closeeyes", "vgui/face/close_eyes", nil, {
faceposer_flex0 = "0",
faceposer_flex1 = "0",
faceposer_flex2 = "1",
faceposer_flex3 = "1",
faceposer_flex4 = "1",
faceposer_flex5 = "1",
faceposer_flex6 = "1",
faceposer_flex7 = "1",
faceposer_flex8 = "1",
faceposer_flex9 = "1"
} )
QuickFace:AddMaterialEx( "#faceposer.angryeyebrows", "vgui/face/angry_eyebrows", nil, {
faceposer_flex10 = "0",
faceposer_flex11 = "0",
faceposer_flex12 = "1",
faceposer_flex13 = "1",
faceposer_flex14 = "0.5",
faceposer_flex15 = "0.5"
} )
QuickFace:AddMaterialEx( "#faceposer.normaleyebrows", "vgui/face/normal_eyebrows", nil, {
faceposer_flex10 = "0",
faceposer_flex11 = "0",
faceposer_flex12 = "0",
faceposer_flex13 = "0",
faceposer_flex14 = "0",
faceposer_flex15 = "0"
} )
QuickFace:AddMaterialEx( "#faceposer.sorryeyebrows", "vgui/face/sorry_eyebrows", nil, {
faceposer_flex10 = "1",
faceposer_flex11 = "1",
faceposer_flex12 = "0",
faceposer_flex13 = "0",
faceposer_flex14 = "0",
faceposer_flex15 = "0"
} )
QuickFace:AddMaterialEx( "#faceposer.grin", "vgui/face/grin", nil, {
faceposer_flex20 = "1",
faceposer_flex21 = "1",
faceposer_flex22 = "1",
faceposer_flex23 = "1",
faceposer_flex24 = "0",
faceposer_flex25 = "0",
faceposer_flex26 = "0",
faceposer_flex27 = "1",
faceposer_flex28 = "1",
faceposer_flex29 = "0",
faceposer_flex30 = "0",
faceposer_flex31 = "0",
faceposer_flex32 = "0",
faceposer_flex33 = "1",
faceposer_flex34 = "1",
faceposer_flex35 = "0",
faceposer_flex36 = "0",
faceposer_flex37 = "0",
faceposer_flex38 = "0",
faceposer_flex39 = "1",
faceposer_flex40 = "0",
faceposer_flex41 = "0",
faceposer_flex42 = "1",
faceposer_flex43 = "1"
} )
QuickFace:AddMaterialEx( "#faceposer.sad", "vgui/face/sad", nil, {
faceposer_flex20 = "0",
faceposer_flex21 = "0",
faceposer_flex22 = "0",
faceposer_flex23 = "0",
faceposer_flex24 = "1",
faceposer_flex25 = "1",
faceposer_flex26 = "0.0",
faceposer_flex27 = "0",
faceposer_flex28 = "0",
faceposer_flex29 = "0",
faceposer_flex30 = "0",
faceposer_flex31 = "0",
faceposer_flex32 = "0",
faceposer_flex33 = "0",
faceposer_flex34 = "0",
faceposer_flex35 = "0",
faceposer_flex36 = "0",
faceposer_flex37 = "0",
faceposer_flex38 = "0.5",
faceposer_flex39 = "0",
faceposer_flex40 = "0",
faceposer_flex41 = "0",
faceposer_flex42 = "0",
faceposer_flex43 = "0"
} )
QuickFace:AddMaterialEx( "#faceposer.smile", "vgui/face/smile", nil, {
faceposer_flex20 = "1",
faceposer_flex21 = "1",
faceposer_flex22 = "1",
faceposer_flex23 = "1",
faceposer_flex24 = "0",
faceposer_flex25 = "0",
faceposer_flex26 = "0",
faceposer_flex27 = "0.6",
faceposer_flex28 = "0.4",
faceposer_flex29 = "0",
faceposer_flex30 = "0",
faceposer_flex31 = "0",
faceposer_flex32 = "0",
faceposer_flex33 = "1",
faceposer_flex34 = "1",
faceposer_flex35 = "0",
faceposer_flex36 = "0",
faceposer_flex37 = "0",
faceposer_flex38 = "0",
faceposer_flex39 = "0",
faceposer_flex40 = "1",
faceposer_flex41 = "1",
faceposer_flex42 = "0",
faceposer_flex43 = "0",
faceposer_flex44 = "0",
} )
CPanel:AddItem( QuickFace )
CPanel:AddControl( "Slider", { Label = "#tool.faceposer.scale", Command = "faceposer_scale", Type = "Float", Min = -5, Max = 5, Help = true, Default = 1 } ):SetHeight( 16 )
CPanel:AddControl( "Button", { Text = "#tool.faceposer.randomize", Command = "faceposer_randomize" } )
local filter = CPanel:AddControl( "TextBox", { Label = "#spawnmenu.quick_filter_tool" } )
filter:SetUpdateOnType( true )
-- Group flex controllers by their type..
local flexGroups = {}
for i = 0, faceEntity:GetFlexNum() - 1 do
local name = faceEntity:GetFlexName( i )
if ( !IsUselessFaceFlex( name ) ) then
local group = faceEntity:GetFlexType( i )
if ( group == name ) then group = "Other" end
local min, max = faceEntity:GetFlexBounds( i )
flexGroups[ group ] = flexGroups[ group ] or {}
table.insert( flexGroups[ group ], { name = name, id = i, min = min, max = max } )
end
end
local flexControllers = {}
for group, items in pairs( flexGroups ) do
local groupForm = vgui.Create( "DForm", CPanel )
groupForm:SetLabel( string.NiceName( group ) )
-- Give the DForm a nice outline
groupForm.GetBackgroundColor = function() return color_white end
function groupForm:Paint( w, h )
derma.SkinHook( "Paint", "CategoryList", self, w, h )
derma.SkinHook( "Paint", "CollapsibleCategory", self, w, h )
end
CPanel:AddItem( groupForm )
for id, item in pairs( items ) do
local ctrl = groupForm:NumSlider( string.NiceName( item.name ), "faceposer_flex" .. item.id, item.min, item.max, 2 )
ctrl:SetDefaultValue( GenerateDefaultFlexValue( faceEntity, item.id ) )
ctrl:SetHeight( 11 ) -- This makes the controls all bunched up like how we want
ctrl:DockPadding( 0, -6, 0, -4 ) -- Try to make the lower part of the text visible
ctrl.originalName = item.name
table.insert( flexControllers, ctrl )
if ( item.id >= MAXSTUDIOFLEXCTRL ) then
ctrl:SetEnabled( false )
ctrl:SetTooltip( "#tool.faceposer.too_many_flexes" )
end
end
-- HACK: Add some padding to the bottom of the list, because Dock won't
local padding = vgui.Create( "Panel", groupForm )
padding:SetHeight( 0 )
groupForm:AddItem( padding )
end
-- Actual searching
filter.OnValueChange = function( pnl, txt )
for id, flxpnl in ipairs( flexControllers ) do
if ( !flxpnl:GetText():lower():find( txt:lower(), nil, true ) && !flxpnl.originalName:lower():find( txt:lower(), nil, true ) ) then
flxpnl:SetVisible( false )
else
flxpnl:SetVisible( true )
end
flxpnl:InvalidateParent()
end
CPanel:InvalidateChildren()
end
end
local FacePoser = surface.GetTextureID( "gui/faceposer_indicator" )
-- Draw a box indicating the face we have selected
function TOOL:DrawHUD()
if ( GetConVarNumber( "gmod_drawtooleffects" ) == 0 ) then return end
local selected = self:FacePoserEntity()
if ( !IsValid( selected ) || selected:IsWorld() || selected:GetFlexNum() == 0 ) then return end
local pos = selected:GetPos()
local eyeattachment = selected:LookupAttachment( "eyes" )
if ( eyeattachment != 0 ) then
local attachment = selected:GetAttachment( eyeattachment )
pos = attachment.Pos
else
-- The model has no "eyes" attachment, try to find a bone with "head" in its name
for i = 0, selected:GetBoneCount() - 1 do
if ( selected:GetBoneName( i ) && selected:GetBoneName( i ):lower():find( "head" ) ) then
pos = selected:GetBonePosition( i )
end
end
end
local scrpos = pos:ToScreen()
if ( !scrpos.visible ) then return end
-- Work out the side distance to give a rough headsize box..
local player_eyes = LocalPlayer():EyeAngles()
local side = ( pos + player_eyes:Right() * 20 ):ToScreen()
local size = math.abs( side.x - scrpos.x )
surface.SetDrawColor( 255, 255, 255, 255 )
surface.SetTexture( FacePoser )
surface.DrawTexturedRect( scrpos.x - size, scrpos.y - size, size * 2, size * 2 )
end