mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-17 21:53:46 +03:00
464 lines
13 KiB
Lua
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
|