Ragdoll mover update (#4)

* Ragdoll mover edit from workshop by Le Hot Doge

* Initial pass, some changes and tweaks for Doge's edit to not throw errors and a bit of formatting

* Added angle showing thing when rotating entities

* Added ability to manipulate non physical bones (like fingers), option to disable entity filter to select any entity (like cameras or lights)

* Streamlined the bone selection process. Fixed bug with pressing the R key

* Update 2 - Fixed error that happened when first using ragoll mover in the map, fixed some weird models causing tool to break, added ability to select effect prop's effect, added new option to disable children physics bones moving with the parent, made it so only bonelist tab updated when changing selected entity

* Tweaks to the gizmos - Rotation gizmo now will not have the issue of rotating stuff in the opposite direction for non physical bones, Plane movement stuffs now work for non physical bones, also effect's root bone gizmos will set its movement angles to be that of effect's base prop

* Made it so gizmos will track yaw of non physical bones, as it seems to move other angle stuff with it

* Tweak to make bones on prop_physics selectable

* Reverted gizmos to track all rotations of the non physical bones. Those are very weird

* Fixed that minor lua error that was happening if you try first using the tool and had manual bone picking enabled
This commit is contained in:
penolakushari 2021-09-01 13:59:39 +03:00 committed by GitHub
parent c5971644d8
commit 091553465c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 677 additions and 158 deletions

View File

@ -1,3 +1,4 @@
Ragdoll Mover v1
Ragdoll Mover v1 edit
================
This is the first version of Ragdoll Mover; the one that's already released.
This is the edit of first version of Ragdoll Mover;
You can help the development by testing it and providing feedback.

View File

@ -212,15 +212,51 @@ function GetOffsetTable(tool,ent,rotate)
CreateDefaultIKs(tool,ent)
end
RTable[0] = {}
RTable[0].pos = ent:GetPhysicsObjectNum(0):GetPos()
RTable[0].ang = ent:GetPhysicsObjectNum(0):GetAngles()
RTable[0].moving = ent:GetPhysicsObjectNum(0):IsMoveable()
local bonestart
--------------------------------------------------------- gotta sort out the bones in case if there are genius sfm to gmod ports with roottransform as 0 bone, like wtf
for a = 0, ent:GetBoneCount() - 1 do
local phys;
local IsPhysBone = false;
for i = 0, ent:GetPhysicsObjectCount() - 1 do
local b = ent:TranslatePhysBoneToBone(i)
if a == b then
phys = i
end
end
local count = ent:GetPhysicsObjectCount()
if count == 0 then
phys = -1
elseif count == 1 then
phys = 0
IsPhysBone = true;
end
if phys and 0 <= phys and count > phys then
if ent:GetPhysicsObjectNum(phys) then
IsPhysBone = true
end
end
--------------------------------------------------------- wait until we get first physics bone, that'll be our parent bone, and not some non physical stuff like roottransform that causes stuff to freak out
if IsPhysBone then
bonestart = a
a = ent:TranslateBoneToPhysBone(a)
RTable[a] = {}
RTable[a].pos = ent:GetPhysicsObjectNum(a):GetPos()
RTable[a].ang = ent:GetPhysicsObjectNum(a):GetAngles()
RTable[a].moving = ent:GetPhysicsObjectNum(a):IsMoveable()
RTable["FirstBone"] = a
RTable["FirstNPHys"] = bonestart
break
end
end
for i=1,ent:GetBoneCount()-1 do
for i=1+bonestart,ent:GetBoneCount()-1 do
local pb = BoneToPhysBone(ent,i)
local parent = GetPhysBoneParent(ent,pb)
if pb and pb != 0 and parent and !RTable[pb] then
if pb and pb != RTable["FirstBone"] and parent and !RTable[pb] then
local b = ent:TranslatePhysBoneToBone(pb)
local bn = ent:GetBoneName(b)
local obj1 = ent:GetPhysicsObjectNum(pb)
@ -271,14 +307,17 @@ end
local function SetBoneOffsets(ent,ostable,sbone)
local RTable = {}
RTable[0] = {}
RTable[0].pos = ostable[0].pos
RTable[0].ang = ostable[0].ang
if sbone.b == 0 then
RTable[0].pos = sbone.p
RTable[0].ang = sbone.a
local firstbone = ostable["FirstBone"]
local firstnphys = ostable["FirstNPHys"]
RTable[firstbone] = {}
RTable[firstbone].pos = ostable[firstbone].pos
RTable[firstbone].ang = ostable[firstbone].ang
if sbone.b == firstbone then
RTable[firstbone].pos = sbone.p
RTable[firstbone].ang = sbone.a
end
for i=1,ent:GetBoneCount()-1 do
for i=1 + firstnphys,ent:GetBoneCount()-1 do
local pb = BoneToPhysBone(ent,i)
if ostable[pb] then
local parent = ostable[pb].parent

View File

@ -81,12 +81,40 @@ hook.Add("PlayerSpawn","rgmSpawn",function(pl)
pl.rgm = {};
pl.rgmSync = Sync;
pl.rgmSyncOne = SyncOne;
pl.rgm.ClientSet = false;
end
end)
if SERVER then
util.AddNetworkString("rgmSync");
util.AddNetworkString("rgmSyncClient");
net.Receive("rgmSyncClient", function(len, ply)
local pl = ply;
if !pl.rgm then pl.rgm = {}; end
local count = net.ReadInt(32);
for i=1, count do
local name = net.ReadString();
local type = net.ReadInt(8);
local value = nil
if type == TYPE_ENTITY then
value = net.ReadEntity();
elseif type == TYPE_NUMBER then
value = net.ReadFloat();
elseif type == TYPE_VECTOR then
value = net.ReadVector();
elseif type == TYPE_ANGLE then
value = net.ReadAngle();
elseif type == TYPE_BOOL then
value = net.ReadBit() == 1;
end
pl.rgm[name] = value;
end
end)
else

View File

@ -53,7 +53,6 @@ end)
function ENT:DrawLines(scale)
local pl = LocalPlayer();
if !self.Axises then return end
local rotate = pl.rgm.Rotate or false;
local collision = self:TestCollision(LocalPlayer(),scale)
@ -61,6 +60,7 @@ function ENT:DrawLines(scale)
local Start,End = 1,6
if rotate then Start,End = 7,10 end
-- print(self.Axises);
if !self.Axises then return end;
for i=Start,End do
local moveaxis = self.Axises[i];
local yellow = false
@ -90,6 +90,24 @@ function ENT:DrawDirectionLine(norm,scale,ghost)
surface.DrawLine(pos1.x,pos1.y,pos2.x,pos2.y)
end
function ENT:DrawAngleText(axis, hitpos, startAngle)
local pos = WorldToLocal(hitpos, Angle(0,0,0), axis:GetPos(), axis:GetAngles())
local overnine
pos = WorldToLocal(pos, pos:Angle(), Vector(0, 0, 0), startAngle:Angle())
local localized = Vector(pos.x, pos.z, 0):Angle()
if(localized.y > 181) then
overnine = 360
else
overnine = 0
end
local textAngle = math.abs(math.Round( (overnine - localized.y) * 100 ) / 100)
local textpos = hitpos:ToScreen()
draw.SimpleText(textAngle,"HudHintTextLarge",textpos.x + 5,textpos.y,Color(0,200,0,255),TEXT_ALIGN_LEFT,TEXT_ALIGN_BOTTOM)
end
function ENT:Draw()
end
function ENT:DrawTranslucent()

View File

@ -11,7 +11,7 @@ util.AddNetworkString("rgmAxis");
util.AddNetworkString("rgmAxisUpdate");
function ENT:Setup()
//Arrows
self.ArrowX = ents.Create("rgm_axis_arrow")
self.ArrowX:SetParent(self)
@ -19,6 +19,7 @@ function ENT:Setup()
self.ArrowX:SetColor(Color(255,0,0,255))
self.ArrowX:SetLocalPos(Vector(0,0,0))
self.ArrowX:SetLocalAngles(Vector(1,0,0):Angle())
self.ArrowX.axistype = 1
self.ArrowY = ents.Create("rgm_axis_arrow")
self.ArrowY:SetParent(self)
@ -26,6 +27,7 @@ function ENT:Setup()
self.ArrowY:SetColor(Color(0,255,0,255))
self.ArrowY:SetLocalPos(Vector(0,0,0))
self.ArrowY:SetLocalAngles(Vector(0,1,0):Angle())
self.ArrowY.axistype = 2
self.ArrowZ = ents.Create("rgm_axis_arrow")
self.ArrowZ:SetParent(self)
@ -33,6 +35,7 @@ function ENT:Setup()
self.ArrowZ:SetColor(Color(0,0,255,255))
self.ArrowZ:SetLocalPos(Vector(0,0,0))
self.ArrowZ:SetLocalAngles(Vector(0,0,1):Angle())
self.ArrowZ.axistype = 3
//Arrow sides
self.ArrowXY = ents.Create("rgm_axis_side")
@ -70,6 +73,7 @@ function ENT:Setup()
self.DiscP:SetNWInt("type",TYPE_DISC)
self.DiscP:SetLocalPos(Vector(0,0,0))
self.DiscP:SetLocalAngles(Vector(0,1,0):Angle())
self.DiscP.axistype = 1 -- axistype is a variable to help with setting non physical bones - 1 for pitch, 2 yaw, 3 roll, 4 for the big one
self.DiscY = ents.Create("rgm_axis_disc")
self.DiscY:SetParent(self)
@ -78,6 +82,7 @@ function ENT:Setup()
self.DiscY:SetNWInt("type",TYPE_DISC)
self.DiscY:SetLocalPos(Vector(0,0,0))
self.DiscY:SetLocalAngles(Vector(0,0,1):Angle())
self.DiscY.axistype = 2
self.DiscR = ents.Create("rgm_axis_disc")
self.DiscR:SetParent(self)
@ -86,6 +91,7 @@ function ENT:Setup()
self.DiscR:SetNWInt("type",TYPE_DISC)
self.DiscR:SetLocalPos(Vector(0,0,0))
self.DiscR:SetLocalAngles(Vector(1,0,0):Angle())
self.DiscR.axistype = 3
self.DiscLarge = ents.Create("rgm_axis_disc_large")
self.DiscLarge:SetParent(self)
@ -95,6 +101,7 @@ function ENT:Setup()
self.DiscLarge:SetNWInt("type",TYPE_DISC)
self.DiscLarge:SetLocalPos(Vector(0,0,0))
self.DiscLarge:SetLocalAngles(Vector(1,0,0):Angle()) //This will be constantly changed
self.DiscLarge.axistype = 4
self.Axises = {
self.ArrowX,
@ -143,25 +150,59 @@ function ENT:Think()
local ent = pl.rgm.Entity;
local bone = pl.rgm.PhysBone;
if !IsValid(ent) then return end
if !IsValid(ent) or !pl.rgm.Bone then return end
local OldPos = self:GetPos();
local OldAng = self:GetAngles();
local OldDiscPos = self.DiscLarge:GetLocalPos();
local OldDiscAng = self.DiscLarge:GetLocalAngles();
local pos, ang;
local rotate = pl.rgm.Rotate or false;
local physobj = ent:GetPhysicsObjectNum(bone)
local pos,ang = physobj:GetPos(),physobj:GetAngles()
if pl.rgm.IsPhysBone then
local physobj = ent:GetPhysicsObjectNum(bone);
if physobj == nil then return end
pos,ang = physobj:GetPos(),physobj:GetAngles();
else
if !pl.rgm.GizmoPos then
local matrix = ent:GetBoneMatrix(bone);
pos = ent:GetBonePosition(bone);
if pos == ent:GetPos() then
pos = matrix:GetTranslation();
end
else
pos = pl.rgm.GizmoPos;
end
if rotate then
if !pl.rgm.GizmoAng then -- dunno if there is a need for these failsafes
_ , ang = ent:GetBonePosition(bone);
else
ang = pl.rgm.GizmoAng;
end
else
if ent:GetBoneParent(bone) ~= -1 then
if !pl.rgm.GizmoParent then
matrix = ent:GetBoneMatrix(ent:GetBoneParent(bone)); -- never would have guessed that when moving bones they use angles of their parent bone rather than their own angles. happened to get to know that after looking at vanilla bone manipulator!
ang = matrix:GetAngles();
else
ang = pl.rgm.GizmoParent;
end
elseif IsValid(pl.rgm.EffectBase) then
ang = pl.rgm.EffectBase:GetAngles();
end
end
end
self:SetPos(pos)
local localstate = self.localizedpos
local rotate = pl.rgm.Rotate or false;
if rotate then localstate = self.localizedang end
if localstate then
self:SetAngles(ang)
else
self:SetAngles(Angle(0,0,0))
if !pl.rgm.Moving then -- Prevent whole thing from rotating when we do localized rotation - needed for proper angle reading
if localstate or !pl.rgm.IsPhysBone then -- Non phys bones don't go well with world coordinates. Well, I didn't make them to behave with those
self:SetAngles(ang or Angle(0,0,0))
else
self:SetAngles(Angle(0,0,0))
end
end
//Updating positions

View File

@ -24,6 +24,7 @@ function ENT:TestCollision(pl,scale)
local Start,End = 1,6
if rotate then Start,End = 7,10 end
local cols = {}
if !self.Axises then return false end;
for i=Start,End do
local e = self.Axises[i];
-- print(e);

View File

@ -3,13 +3,24 @@ include("shared.lua")
AddCSLuaFile("cl_init.lua")
AddCSLuaFile("shared.lua")
function ENT:ProcessMovement(offpos,offang,eyepos,eyeang,ent,bone,ppos,pnorm)
local obj = ent:GetPhysicsObjectNum(bone)
function ENT:ProcessMovement(offpos,offang,eyepos,eyeang,ent,bone,ppos,pnorm, isphys, StartGrab, NPhysPos)
local intersect = self:GetGrabPos(eyepos,eyeang,ppos)
local localized = self:WorldToLocal(intersect)
localized = Vector(localized.x,0,0)
intersect = self:LocalToWorld(localized)
local ang = obj:GetAngles()
local pos,_a = LocalToWorld(Vector(offpos.x,0,0),Angle(0,0,0),intersect,self:GetAngles())
local pos, ang
if isphys then
local _a
local obj = ent:GetPhysicsObjectNum(bone)
localized = Vector(localized.x,0,0)
intersect = self:LocalToWorld(localized)
ang = obj:GetAngles()
pos,_a = LocalToWorld(Vector(offpos.x,0,0),Angle(0,0,0),intersect,self:GetAngles())
else
pos = ent:GetManipulateBonePosition(bone)
localized = Vector(localized.x - StartGrab.x,0,0)
local posadd = NPhysPos[self.axistype] + localized.x
ang = ent:GetManipulateBoneAngles(bone)
pos[self.axistype] = posadd
end
return pos,ang
end

View File

@ -3,12 +3,73 @@ include("shared.lua")
AddCSLuaFile("cl_init.lua")
AddCSLuaFile("shared.lua")
function ENT:ProcessMovement(offpos,offang,eyepos,eyeang,ent,bone,ppos,pnorm)
local function ConvertVector(vec, axistype)
local rotationtable, result
if axistype == 1 then
result = Vector(-vec.x, vec.z, 0)
elseif axistype == 2 then
result = Vector(vec.x, vec.y, 0)
elseif axistype == 3 then
result = Vector(vec.y, vec.z, 0)
else
result = vec
end
return result
end
function ENT:ProcessMovement(offpos,offang,eyepos,eyeang,ent,bone,ppos,pnorm, isphys, startAngle, garbage, NPhysAngle) -- initially i had a table instead of separate things for initial bone pos and angle, but sync command can't handle tables and i thought implementing a way to handle those would be too much hassle
local intersect = self:GetGrabPos(eyepos,eyeang,ppos,pnorm)
local localized = self:WorldToLocal(intersect)
localized = Vector(localized.y,localized.z,0):Angle()
local pos = self:GetPos()
local ang = self:LocalToWorldAngles(Angle(0,0,localized.y))
local _p,_a = LocalToWorld(Vector(0,0,0),offang,pos,ang)
return pos,_a
end
local _p, _a
local pl = self:GetParent().Owner
local axistable = {
(self:GetParent():LocalToWorld(Vector(0,1,0)) - self:GetPos()):Angle(),
(self:GetParent():LocalToWorld(Vector(0,0,1)) - self:GetPos()):Angle(),
(self:GetParent():LocalToWorld(Vector(1,0,0)) - self:GetPos()):Angle(),
(self:GetPos()-pl:EyePos()):Angle()
}
if isphys then
localized = Vector(localized.y,localized.z,0):Angle()
local pos = self:GetPos()
local ang = self:LocalToWorldAngles(Angle(0,0,localized.y))
_p,_a = LocalToWorld(Vector(0,0,0),offang,pos,ang)
_p = pos
else
local rotateang, axisangle
axisangle = axistable[self.axistype]
--[[ _a = ent:GetManipulateBoneAngles(bone)
localized = WorldToLocal(localized, localized:Angle(), Vector(0, 0, 0), startAngle:Angle())
localized = Vector(localized.x, localized.z, 0):Angle()
rotateang = NPhysAngle[self.axistype] + localized.y -- putting it in another variable to avoid constant adding onto the angle variable
_a[self.axistype] = rotateang]]
local _, boneang = ent:GetBonePosition(bone)
local startlocal = LocalToWorld(startAngle, startAngle:Angle(), Vector(0,0,0), axisangle) -- first we get our vectors into world coordinates, relative to the axis angles
localized = LocalToWorld(localized, localized:Angle(), Vector(0,0,0), axisangle)
localized = WorldToLocal(localized, localized:Angle(), Vector(0,0,0), boneang) -- then convert that vector to the angles of the bone
startlocal = WorldToLocal(startlocal, startlocal:Angle(), Vector(0,0,0), boneang)
localized = ConvertVector(localized, self.axistype)
startlocal = ConvertVector(startlocal, self.axistype)
localized = localized:Angle() - startlocal:Angle()
if self.axistype == 4 then
rotateang = NPhysAngle + localized
_a = rotateang
else
_a = ent:GetManipulateBoneAngles(bone)
rotateang = NPhysAngle[self.axistype] + localized.y
_a[self.axistype] = rotateang
end
_p = ent:GetManipulateBonePosition(bone)
end
return _p,_a
end

View File

@ -3,7 +3,9 @@ ENT.Type = "anim"
ENT.Base = "base_entity"
function ENT:Initialize()
self:SetNoDraw(true)
if CLIENT then
self:SetNoDraw(true)
end
self:DrawShadow(false)
self:SetCollisionBounds(Vector(-0.1,-0.1,-0.1),Vector(0.1,0.1,0.1))
self:SetSolid(SOLID_VPHYSICS)

View File

@ -3,10 +3,35 @@ include("shared.lua")
AddCSLuaFile("cl_init.lua")
AddCSLuaFile("shared.lua")
function ENT:ProcessMovement(offpos,offang,eyepos,eyeang,ent,bone,ppos,pnorm)
local obj = ent:GetPhysicsObjectNum(bone)
function ENT:ProcessMovement(offpos,offang,eyepos,eyeang,ent,bone,ppos,pnorm, isphys, startGrab, NPhysPos)
local intersect = self:GetGrabPos(eyepos,eyeang,ppos,pnorm)
local ang = obj:GetAngles()
local pos,_a = LocalToWorld(offpos,Angle(0,0,0),intersect,self:GetAngles())
local pos, ang
local pl = self:GetParent().Owner
if isphys then
local obj = ent:GetPhysicsObjectNum(bone)
ang = obj:GetAngles()
pos = LocalToWorld(offpos,Angle(0,0,0),intersect,self:GetAngles())
else
local localized, startmove, finalpos, boneang
if ent:GetBoneParent(bone) ~= -1 then
local matrix = ent:GetBoneMatrix(ent:GetBoneParent(bone))
boneang = matrix:GetAngles();
else
if IsValid(pl.rgm.EffectBase) then
boneang = pl.rgm.EffectBase:GetAngles()
else
boneang = Angle(0,0,0)
end
end
localized = LocalToWorld(offpos,Angle(0,0,0),intersect,self:GetAngles())
localized = WorldToLocal(localized, Angle(0,0,0), self:GetPos(), boneang)
finalpos = NPhysPos + localized
ang = ent:GetManipulateBoneAngles(bone)
pos = finalpos
end
return pos,ang
end

View File

@ -8,6 +8,14 @@ TOOL.ClientConVar["localpos"] = 0
TOOL.ClientConVar["localang"] = 1
TOOL.ClientConVar["scale"] = 10
TOOL.ClientConVar["fulldisc"] = 0
TOOL.ClientConVar["manual"] = 0
TOOL.ClientConVar["boneid"] = 0
TOOL.ClientConVar["entityholder"] = "some"
TOOL.ClientConVar["entity"] = ""
TOOL.ClientConVar["resetbone"] = 0
TOOL.ClientConVar["disablefilter"] = 0
TOOL.ClientConVar["disablechildbone"] = 0
TOOL.ClientConVar["selecteffects"] = 0
TOOL.ClientConVar["ik_leg_L"] = 0
TOOL.ClientConVar["ik_leg_R"] = 0
@ -21,12 +29,112 @@ TOOL.ClientConVar["updaterate"] = 0.01
TOOL.ClientConVar["rotatebutton"] = MOUSE_MIDDLE
TOOL.ClientConVar["boneidmax"] = 20
TOOL.ClientConVar["boneidmaxholder"] = 20
RunConsoleCommand("ragdollmover_boneid",0)
concommand.Add("ragdollmover_resetroot", function(pl)
pl.rgm.IsPhysBone = true;
pl.rgm.PhysBone = 0;
pl.rgm.Bone = 0;
RunConsoleCommand("ragdollmover_boneid",0);
pl:rgmSync();
end)
local TransTable = {
"ArrowX", "ArrowY", "ArrowZ",
"ArrowXY", "ArrowXZ", "ArrowYZ",
"DiscP", "DiscY", "DiscR"
}
local function SyncOneClient(self, name)
if SERVER or !self.rgm then return end
local v = self.rgm[name];
if v == nil then return end
net.Start("rgmSyncClient");
local count = 1;
net.WriteInt(count, 32);
net.WriteString(name);
local Type = string.lower(type(v));
if Type == "entity" then
net.WriteInt(1, 8); -- Int's correspond to the type of data we pass, for more info check ragdollmover_meta.lua, since that's where i took the function from
net.WriteEntity(v);
elseif Type == "number" then
net.WriteInt(2, 8);
net.WriteFloat(v);
elseif Type == "vector" then
net.WriteInt(3, 8);
net.WriteVector(v);
elseif Type == "angle" then
net.WriteInt(4, 8);
net.WriteAngle(v);
elseif Type == "boolean" then
net.WriteInt(5, 8);
net.WriteBit(v);
end
net.SendToServer();
end
local function RGMGetBone(pl, ent, bone)
--------------------------------------------------------- yeah this part is from locrotscale
local phys, physobj;
local manual = tobool(GetConVarNumber("ragdollmover_manual"));
pl.rgm.IsPhysBone = false;
for i = 0, ent:GetPhysicsObjectCount() - 1 do
local b = ent:TranslatePhysBoneToBone(i);
if bone == b then
phys = i;
end
end
local count = ent:GetPhysicsObjectCount()
if count == 0 then
phys = -1;
elseif count == 1 then
if ent:GetBoneCount() <= 1 then
phys = 0;
pl.rgm.IsPhysBone = true;
end
end
if phys and 0 <= phys and count > phys then
physobj = ent:GetPhysicsObjectNum(phys);
if physobj then
pl.rgm.IsPhysBone = true;
end
end
---------------------------------------------------------
if manual and tobool(GetConVarNumber("ragdollmover_selecteffects")) then
if phys == -1 then phys = nil end
end
local bonen = phys or bone;
pl.rgm.PhysBone = bonen;
pl.rgm.Bone = bonen;
end
function TOOL:Deploy()
if SERVER then
local pl = self:GetOwner();
local axis = pl.rgm.Axis;
if !IsValid(axis) then
axis = ents.Create("rgm_axis");
axis:Spawn();
axis.Owner = pl;
pl.rgm.Axis = axis;
end
end
end
function TOOL:LeftClick(tr)
if CLIENT then return false end
@ -36,36 +144,39 @@ function TOOL:LeftClick(tr)
if pl.rgm.Moving then return false end
local axis = pl.rgm.Axis;
if !IsValid(axis) then
axis = ents.Create("rgm_axis")
axis:Spawn()
axis.Owner = pl;
axis:Setup()
pl.rgm.Axis = axis;
if !axis.Axises then
axis:Setup();
end
local collision = axis:TestCollision(pl,self:GetClientNumber("scale",10))
local ent = pl.rgm.Entity;
local entstr = tostring(ent)
RunConsoleCommand("ragdollmover_entity",entstr)
if collision and IsValid(ent) then
if _G["physundo"] and _G["physundo"].Create then
_G["physundo"].Create(ent,pl)
end
local apart = collision.axis
pl.rgmISPos = collision.hitpos*1
pl.rgmISDir = apart:GetAngles():Forward()
pl.rgmOffsetTable = rgm.GetOffsetTable(self, ent, pl.rgm.Rotate)
pl.rgmOffsetPos = WorldToLocal(apart:GetPos(),apart:GetAngles(),collision.hitpos,apart:GetAngles())
local opos = apart:WorldToLocal(collision.hitpos)
local obj = ent:GetPhysicsObjectNum(pl.rgm.PhysBone)
local grabang = apart:LocalToWorldAngles(Angle(0,0,Vector(opos.y,opos.z,0):Angle().y))
local _p
_p,pl.rgmOffsetAng = WorldToLocal(apart:GetPos(),obj:GetAngles(),apart:GetPos(),grabang)
if obj then
_p,pl.rgmOffsetAng = WorldToLocal(apart:GetPos(),obj:GetAngles(),apart:GetPos(),grabang)
pl.rgmOffsetTable = rgm.GetOffsetTable(self, ent, pl.rgm.Rotate)
end
pl.rgm.StartAngle = WorldToLocal(collision.hitpos, Angle(0,0,0), apart:GetPos(), apart:GetAngles())
pl.rgm.NPhysBonePos = ent:GetManipulateBonePosition(pl.rgm.Bone)
pl.rgm.NPhysBoneAng = ent:GetManipulateBoneAngles(pl.rgm.Bone)
local dirnorm = (collision.hitpos-axis:GetPos())
dirnorm:Normalize()
@ -77,24 +188,57 @@ function TOOL:LeftClick(tr)
pl.rgm.MoveAxis = apart;
pl.rgm.KeyDown = true;
pl.rgm.Moving = true;
pl:rgmSync();
return false
end
if IsValid(tr.Entity) and (tr.Entity:GetClass() == "prop_ragdoll" or tr.Entity:GetClass() == "prop_physics") then
-- pl:SetNWInt("ragdollmover_physbone",tr.PhysicsBone)
-- pl:SetNWInt("ragdollmover_bone",tr.Entity:TranslatePhysBoneToBone(tr.PhysicsBone))
-- pl:SetNWEntity("ragdollmover_ent",tr.Entity)
-- pl:SetNWBool("ragdollmover_draw",true)
pl.rgm.PhysBone = tr.PhysicsBone;
pl.rgm.Bone = tr.Entity:TranslatePhysBoneToBone(tr.PhysicsBone);
pl.rgm.Entity = tr.Entity;
pl.rgm.Draw = true;
if IsValid(tr.Entity) and ( (tr.Entity:GetClass() == "prop_ragdoll" or tr.Entity:GetClass() == "prop_physics" or tr.Entity:GetClass() == "prop_effect" ) or tobool(self:GetClientNumber("disablefilter",0)) ) then
local entity
pl:rgmSync();
if tobool(self:GetClientNumber("manual",0)) and tobool(self:GetClientNumber("selecteffects",0)) and IsValid(tr.Entity.AttachedEntity) then
pl:SetNWEntity("ragdollmover_ent",tr.Entity.AttachedEntity) -- if manual bone selection is enabled and we select props, we select attached entity (the prop of effect prop, if that makes sense)
pl.rgm.EffectBase = tr.Entity
entity = tr.Entity.AttachedEntity
pl.rgm.Entity = tr.Entity.AttachedEntity
pl.rgm.Draw = true
else
-- pl:SetNWInt("ragdollmover_physbone",tr.PhysicsBone)
-- pl:SetNWInt("ragdollmover_bone",tr.Entity:TranslatePhysBoneToBone(tr.PhysicsBone))
pl:SetNWEntity("ragdollmover_ent",tr.Entity)
-- pl:SetNWBool("ragdollmover_draw",true)
entity = tr.Entity
pl.rgm.Entity = tr.Entity;
pl.rgm.EffectBase = nil;
pl.rgm.Draw = true;
end
if not entity.rgmbonecached then -- also taken from locrotscale. some hacky way to cache the bones?
local p = self.SWEP:GetParent();
self.SWEP:FollowBone(entity, 0);
self.SWEP:SetParent(p);
entity.rgmbonecached = true;
end
if !tobool(self:GetClientNumber("manual",0)) then
pl.rgm.PhysBone = tr.PhysicsBone;
pl.rgm.Bone = entity:TranslatePhysBoneToBone(tr.PhysicsBone);
pl.rgm.IsPhysBone = true;
end
local bonecount = entity:GetBoneCount() - 1
if bonecount then
RunConsoleCommand("ragdollmover_boneidmax", bonecount);
end
if ((GetConVarNumber("ragdollmover_boneidmaxholder") ~= GetConVarNumber("ragdollmover_boneidmax"))
or (GetConVarString("ragdollmover_entity") ~= GetConVarString("ragdollmover_entityholder")))
and tobool(self:GetClientNumber("manual",0)) then
RunConsoleCommand("ragdollmover_resetroot");
else
pl:rgmSync();
end
end
return false
@ -111,22 +255,81 @@ function TOOL:Reload()
local pl = self:GetOwner();
pl.rgm.PhysBone = 0;
pl.rgm.Bone = 0;
pl:rgmSync();
RunConsoleCommand("ragdollmover_resetroot")
return false
end
if SERVER then
function TOOL:Think()
if CLIENT then
local pl = self:GetOwner();
if !pl.rgm then return end
if !pl.rgm.ClientSet then -- setting special function for syncing client to server variables, since hook in ragdollmover_meta only works serverside
pl.rgmSyncClient = SyncOneClient;
pl.rgm.ClientSet = true;
end
if (GetConVarNumber("ragdollmover_boneidmaxholder") ~= GetConVarNumber("ragdollmover_boneidmax")) then
RunConsoleCommand("ragdollmover_boneidmaxholder", GetConVarNumber("ragdollmover_boneidmax"));
--ripped from default faceposer
self:UpdateFaceControlPanel();
return;
elseif (GetConVarString("ragdollmover_entity") ~= GetConVarString("ragdollmover_entityholder")) then
RunConsoleCommand("ragdollmover_entityholder", GetConVarString("ragdollmover_entity"));
--ripped from default faceposer
self:UpdateFaceControlPanel();
return;
end
if pl.rgm.Moving then return end -- don't want to keep updating this stuff when we move stuff, so it'll go smoother
local ent, axis = pl.rgm.Entity, pl.rgm.Axis; -- so, this thing... bone position and angles seem to work clientside best, whereas server's ones are kind of shite
if IsValid(ent) and IsValid(axis) and pl.rgm.Bone then
local bone = pl.rgm.Bone;
local pos, ang = ent:GetBonePosition(bone);
if pos == ent:GetPos() then
local matrix = ent:GetBoneMatrix(bone);
pos = matrix:GetTranslation();
ang = matrix:GetAngles();
end
if ent:GetBoneParent(bone) ~= -1 then
local matrix = ent:GetBoneMatrix(ent:GetBoneParent(bone))
local ang = matrix:GetAngles();
pl.rgm.GizmoParent = ang;
else
pl.rgm.GizmoParent = nil;
end
pl.rgm.GizmoPos = pos;
pl.rgm.GizmoAng = ang;
pl:rgmSyncClient("GizmoPos");
pl:rgmSyncClient("GizmoAng");
pl:rgmSyncClient("GizmoParent");
else
pl.rgm.GizmoPos = nil;
pl.rgm.GizmoAng = nil;
pl.rgm.GizmoParent = nil;
pl:rgmSyncClient("GizmoPos");
pl:rgmSyncClient("GizmoAng");
pl:rgmSyncClient("GizmoParent");
end
end
if SERVER then
if !self.LastThink then self.LastThink = CurTime() end
if CurTime() < self.LastThink + self:GetClientNumber("updaterate",0.01) then return end
local pl = self:GetOwner()
local ent = pl.rgm.Entity;
--[[ physboneid = ent:TranslatePhysBoneToBone(GetConVarNumber("ragdollmover_boneid"))
RunConsoleCommand("ragdollmover_boneidlabel", ent:GetBoneName(physboneid)) ]]
if pl.rgm.Bone ~= GetConVarNumber("ragdollmover_boneid") and tobool(self:GetClientNumber("manual",0)) and IsValid(ent) then
RGMGetBone(pl, ent, self:GetClientNumber( "boneid",0 ))
pl:rgmSync()
end
local axis = pl.rgm.Axis;
if IsValid(axis) then
@ -138,6 +341,12 @@ function TOOL:Think()
end
end
if GetConVarNumber("ragdollmover_resetbone") ~= 0 then
RunConsoleCommand("ragdollmover_resetbone", 0)
ent:ManipulateBoneAngles(pl.rgm.Bone, Angle(0, 0, 0))
ent:ManipulateBonePosition(pl.rgm.Bone, Vector(0, 0, 0))
end
local moving = pl.rgm.Moving or false;
local rotate = pl.rgm.Rotate or false;
if moving then
@ -149,81 +358,106 @@ function TOOL:Think()
local apart = pl.rgm.MoveAxis;
local bone = pl.rgm.PhysBone;
local ent = pl.rgm.Entity;
if !IsValid(ent) then
pl.rgm.Moving = false;
return
end
local isik,iknum = rgm.IsIKBone(self,ent,bone)
local physbonecount = ent:GetBoneCount() - 1
if physbonecount == nil then return end
RunConsoleCommand("ragdollmover_boneidmax", physbonecount)
local pos,ang = apart:ProcessMovement(pl.rgmOffsetPos,pl.rgmOffsetAng,eyepos,eyeang,ent,bone,pl.rgmISPos,pl.rgmISDir)
if pl.rgm.IsPhysBone then
local obj = ent:GetPhysicsObjectNum(bone)
if !isik or iknum == 3 or (rotate and (iknum == 1 or iknum == 2)) then
obj:EnableMotion(true)
obj:Wake()
obj:SetPos(pos)
obj:SetAngles(ang)
obj:EnableMotion(false)
obj:Wake()
elseif iknum == 2 then
for k,v in pairs(ent.rgmIKChains) do
if v.knee == bone then
local intersect = apart:GetGrabPos(eyepos,eyeang)
local obj1 = ent:GetPhysicsObjectNum(v.hip)
local obj2 = ent:GetPhysicsObjectNum(v.foot)
local kd = (intersect-(obj2:GetPos()+(obj1:GetPos()-obj2:GetPos())))
kd:Normalize()
ent.rgmIKChains[k].ikkneedir = kd*1
end
end
end
local postable = rgm.SetOffsets(self,ent,pl.rgmOffsetTable,{b = bone,p = obj:GetPos(),a = obj:GetAngles()})
local sbik,sbiknum = rgm.IsIKBone(self,ent,bone)
if !sbik or sbiknum != 2 then
postable[bone].dontset = true
end
for i=0,ent:GetPhysicsObjectCount()-1 do
if postable[i] and !postable[i].dontset then
local obj = ent:GetPhysicsObjectNum(i)
-- postable[i].pos.x = math.Round(postable[i].pos.x,3)
-- postable[i].pos.y = math.Round(postable[i].pos.y,3)
-- postable[i].pos.z = math.Round(postable[i].pos.z,3)
-- postable[i].ang.p = math.Round(postable[i].ang.p,3)
-- postable[i].ang.y = math.Round(postable[i].ang.y,3)
-- postable[i].ang.r = math.Round(postable[i].ang.r,3)
local poslen = postable[i].pos:Length();
local anglen = Vector(postable[i].ang.p,postable[i].ang.y,postable[i].ang.r):Length();
//Temporary solution for INF and NaN decimals crashing the game (Even rounding doesnt fix it)
if poslen > 2 and anglen > 2 then
obj:EnableMotion(true)
obj:Wake()
obj:SetPos(postable[i].pos)
obj:SetAngles(postable[i].ang)
obj:EnableMotion(false)
obj:Wake()
end
end
end
-- if !pl:GetNWBool("ragdollmover_keydown") then
if !pl:KeyDown(IN_ATTACK) then
if self:GetClientNumber("unfreeze",1) > 0 then
for i=0,ent:GetPhysicsObjectCount()-1 do
if pl.rgmOffsetTable[i].moving then
local obj = ent:GetPhysicsObjectNum(i)
obj:EnableMotion(true)
obj:Wake()
local isik,iknum = rgm.IsIKBone(self,ent,bone)
local pos,ang = apart:ProcessMovement(pl.rgmOffsetPos,pl.rgmOffsetAng,eyepos,eyeang,ent,bone,pl.rgmISPos,pl.rgmISDir, true)
local obj = ent:GetPhysicsObjectNum(bone)
if !isik or iknum == 3 or (rotate and (iknum == 1 or iknum == 2)) then
obj:EnableMotion(true)
obj:Wake()
obj:SetPos(pos)
obj:SetAngles(ang)
obj:EnableMotion(false)
obj:Wake()
elseif iknum == 2 then
for k,v in pairs(ent.rgmIKChains) do
if v.knee == bone then
local intersect = apart:GetGrabPos(eyepos,eyeang)
local obj1 = ent:GetPhysicsObjectNum(v.hip)
local obj2 = ent:GetPhysicsObjectNum(v.foot)
local kd = (intersect-(obj2:GetPos()+(obj1:GetPos()-obj2:GetPos())))
kd:Normalize()
ent.rgmIKChains[k].ikkneedir = kd*1
end
end
end
pl.rgm.Moving = false;
pl:rgmSyncOne("Moving");
if !tobool(self:GetClientNumber("disablechildbone",0)) then
local postable = rgm.SetOffsets(self,ent,pl.rgmOffsetTable,{b = bone,p = obj:GetPos(),a = obj:GetAngles()})
if postable == nil then return end
local firstbone = pl.rgmOffsetTable["FirstBone"]
local sbik,sbiknum = rgm.IsIKBone(self,ent,bone)
if !sbik or sbiknum != 2 then
postable[bone].dontset = true
end
for i=0 + firstbone,ent:GetPhysicsObjectCount()-1 do
if postable[i] and !postable[i].dontset then
local obj = ent:GetPhysicsObjectNum(i)
-- postable[i].pos.x = math.Round(postable[i].pos.x,3)
-- postable[i].pos.y = math.Round(postable[i].pos.y,3)
-- postable[i].pos.z = math.Round(postable[i].pos.z,3)
-- postable[i].ang.p = math.Round(postable[i].ang.p,3)
-- postable[i].ang.y = math.Round(postable[i].ang.y,3)
-- postable[i].ang.r = math.Round(postable[i].ang.r,3)
local poslen = postable[i].pos:Length();
local anglen = Vector(postable[i].ang.p,postable[i].ang.y,postable[i].ang.r):Length();
//Temporary solution for INF and NaN decimals crashing the game (Even rounding doesnt fix it)
if poslen > 2 and anglen > 2 then
obj:EnableMotion(true)
obj:Wake()
obj:SetPos(postable[i].pos)
obj:SetAngles(postable[i].ang)
obj:EnableMotion(false)
obj:Wake()
end
end
end
end
-- if !pl:GetNWBool("ragdollmover_keydown") then
if !pl:KeyDown(IN_ATTACK) then
if self:GetClientNumber("unfreeze",1) > 0 then
for i=0,ent:GetPhysicsObjectCount()-1 do
if pl.rgmOffsetTable[i].moving then
local obj = ent:GetPhysicsObjectNum(i)
obj:EnableMotion(true)
obj:Wake()
end
end
end
pl.rgm.Moving = false;
pl:rgmSyncOne("Moving");
end
else
local pos, ang = apart:ProcessMovement(pl.rgmOffsetPos,pl.rgmOffsetAng,eyepos,eyeang,ent,bone,pl.rgmISPos,pl.rgmISDir, false, pl.rgm.StartAngle, pl.rgm.NPhysBonePos, pl.rgm.NPhysBoneAng) -- if a bone is not physics one, we pass over "start angle" thing
ent:ManipulateBoneAngles(bone, ang)
ent:ManipulateBonePosition(bone, pos)
if !pl:KeyDown(IN_ATTACK) then -- don't think entity has to be unfrozen if you were working with non phys bones, that would be weird?
pl.rgm.Moving = false;
pl:rgmSyncOne("Moving");
end
end
end
@ -251,6 +485,7 @@ local function CCheckBox(cpanel,text,cvar)
local CB = vgui.Create("DCheckBoxLabel",cpanel)
CB:SetText(text)
CB:SetConVar(cvar)
CB:SetDark(true)
cpanel:AddItem(CB)
return CB
end
@ -260,7 +495,10 @@ local function CNumSlider(cpanel,text,cvar,min,max,dec)
SL:SetDecimals(dec)
SL:SetMinMax(min,max)
SL:SetConVar(cvar)
SL:SetDark(true)
cpanel:AddItem(SL)
return SL
end
local function CCol(cpanel,text)
@ -274,15 +512,53 @@ local function CCol(cpanel,text)
col:EnableHorizontal(false)
col:EnableVerticalScrollbar(true)
col.Paint = function()
surface.SetDrawColor(100,100,100,255)
surface.DrawRect(0, 0, 500, 500)
end
cat:SetContents(col)
return col
return col, cat
end
function TOOL.BuildCPanel(CPanel)
local function RGMResetButton(cpanel)
local pl = LocalPlayer()
if !pl.rgm then return end
local ent = pl.rgm.Entity
if !IsValid(ent) then return end
local butt = vgui.Create("DButton", cpanel)
butt:SetText("Reset Non-Physics Bone")
function butt:DoClick()
if !IsValid(ent) then return end
RunConsoleCommand("ragdollmover_resetbone", 1)
end
cpanel:AddItem(butt)
end
local colbones
local category
local Col4
local function RGMBuildBoneMenu(ent, cpanel)
if category then
colbones:Remove()
category:Remove()
end
colbones, category = CCol(cpanel, "Bone List")
RGMResetButton(colbones)
CNumSlider(colbones,"BoneID","ragdollmover_boneid",0,GetConVarNumber("ragdollmover_boneidmax"),0)
if !IsValid(ent) then return end
local num = GetConVarNumber("ragdollmover_boneidmax")
for i = 0,num do
local text1 = ent:GetBoneName(i)
local butt = vgui.Create("DButton", colbones)
butt:SetText(text1)
function butt:DoClick() --think making a function to call a console command is better than making another console command... to call another console command
RunConsoleCommand("ragdollmover_boneid",i)
end
colbones:AddItem(butt)
--:AddControl("Button",{text = text1, Command = cmd})
end
end
function TOOL.BuildCPanel(CPanel, ent)
CPanel:AddControl("Header",{Name = "#Tool_ragdollmover_name","#Tool_ragdollmover_desc"})
CPanel:SetSpacing(3)
@ -292,7 +568,7 @@ function TOOL.BuildCPanel(CPanel)
CCheckBox(Col1,"Localized angle gizmo.","ragdollmover_localang")
CNumSlider(Col1,"Scale","ragdollmover_scale",1.0,50.0,1)
CCheckBox(Col1,"Fully visible discs.","ragdollmover_fulldisc")
local Col2 = CCol(CPanel,"IK Chains")
CCheckBox(Col2,"Left Hand IK","ragdollmover_ik_hand_L")
CCheckBox(Col2,"Right Hand IK","ragdollmover_ik_hand_R")
@ -302,8 +578,9 @@ function TOOL.BuildCPanel(CPanel)
local Col3 = CCol(CPanel,"Misc")
local CB = CCheckBox(Col3,"Unfreeze on release.","ragdollmover_unfreeze")
CB:SetToolTip("Unfreeze bones that were unfrozen before grabbing the ragdoll.")
local DisFil = CCheckBox(Col3, "Disable entity filter.","ragdollmover_disablefilter")
DisFil:SetToolTip("Disable entity filter to select ANY entity. CAUTION - may be buggy")
CNumSlider(Col3,"Tool update rate.","ragdollmover_updaterate",0.01,1.0,2)
-- CCheckBox(Col3, "Use right mouse button.", "ragdollmover_use_rmb");
CPanel:AddControl( "Numpad", { Label = "Move/Rotate toggle button", Command = "ragdollmover_rotatebutton" } )
-- local B = vgui.Create("DButton", CPanel);
@ -311,9 +588,26 @@ function TOOL.BuildCPanel(CPanel)
-- B:SetToolTip("This must be pressed so that the move/rotate button is changed.");
-- B.DoClick = function() LocalPlayer():ConCommand("ragdollmover_changebutton"); end
Col4 = CCol(CPanel, "Bone Manipulation")
local manual = CCheckBox(Col4,"Manual Bone Picking","ragdollmover_manual")
manual:SetToolTip("Enable bone selection through the bone menu. Select bone ID and then click on the selected ragdoll to pick that bone.")
local disableoffset = CCheckBox(Col4, "Disable Child Bone Offset", "ragdollmover_disablechildbone")
disableoffset:SetToolTip("Disable child bone offset (Example: When you rotate pelvis, angles of other bones will be the same)")
local effectselect = CCheckBox(Col4, "Select Effects", "ragdollmover_selecteffects")
effectselect:SetToolTip("MAKE SURE MANUAL BONE PICKING IS ENABLED. Allows you to manipulate bones of the effect props.")
RGMBuildBoneMenu(ent, Col4)
//CPanel:SetHeight(500)
end
function TOOL:UpdateFaceControlPanel( index )
local pl = self:GetOwner()
local ent = pl.rgm.Entity
RGMBuildBoneMenu(ent, Col4)
end
function TOOL:DrawHUD()
local pl = LocalPlayer()
@ -325,13 +619,10 @@ function TOOL:DrawHUD()
local ent = pl.rgm.Entity;
local bone = pl.rgm.Bone;
local axis = pl.rgm.Axis;
local dodraw = pl.rgm.Draw or false;
local moving = pl.rgm.Moving or false;
//We don't draw the axis if we don't have the axis entity or the target entity,
//or if we're not allowed to draw it.
if IsValid(ent) and IsValid(axis) and bone and dodraw then
if IsValid(ent) and IsValid(axis) and bone then
local scale = self:GetClientNumber("scale",10)
local rotate = pl.rgm.Rotate or false;
local moveaxis = pl.rgm.MoveAxis;
@ -344,6 +635,7 @@ function TOOL:DrawHUD()
axis:DrawDirectionLine(fwd,scale,false)
local dirnorm = pl.rgm.DirNorm or Vector(1,0,0);
axis:DrawDirectionLine(dirnorm,scale,true)
axis:DrawAngleText(moveaxis, intersect, pl.rgm.StartAngle)
end
else
axis:DrawLines(scale)
@ -353,7 +645,7 @@ function TOOL:DrawHUD()
local tr = pl:GetEyeTrace()
local aimedbone = pl.rgm.AimedBone or 0;
if IsValid(tr.Entity) and (tr.Entity:GetClass() == "prop_ragdoll" or tr.Entity:GetClass() == "prop_physics")
if IsValid(tr.Entity) and (tr.Entity:GetClass() == "prop_ragdoll" or tr.Entity:GetClass() == "prop_physics" or tr.Entity:GetClass() == "prop_effect")
and (!bone or aimedbone != bone) and !moving then
rgm.DrawBoneName(tr.Entity,aimedbone);
-- if (!bone or aimedbone != bone) and !pl:GetNWBool("ragdollmover_moving",false) then
@ -364,8 +656,8 @@ function TOOL:DrawHUD()
-- end
-- _pos = _pos:ToScreen()
-- local textpos = {x = _pos.x+5,y = _pos.y-5}
-- surface.DrawCircle(_pos.x,_pos.y,2.5,Color(0,200,0,255))
-- draw.SimpleText(name,"Default",textpos.x,textpos.y,Color(0,200,0,255),TEXT_ALIGN_LEFT,TEXT_ALIGN_BOTTOM)
-- surface.DrawCircle(_pos.x,_pos.y,2.5,Color(0,0,0,255))
-- draw.SimpleText(name,"Default",textpos.x,textpos.y,Color(0,0,0,255),TEXT_ALIGN_LEFT,TEXT_ALIGN_BOTTOM)
-- end
end

View File

@ -9,8 +9,8 @@ TOOL.ClientConVar["type"] = "Left Leg"
local ikchains_iktypes = {
"Left Leg",
"Right Leg",
"Left Hand",
"Right Hand"
"Left Arm",
"Right Arm"
}
local function Message(ply,text,icon,sound)
@ -73,24 +73,24 @@ if CLIENT then
language.Add("tool.ragmover_ikchains.name","Ragdoll Mover - IK Chains")
language.Add("tool.ragmover_ikchains.desc","Make your own IK chains for ragdolls to be used with Ragdoll Mover.")
language.Add("tool.ragmover_ikchains.0","Left click to select IK hip bone.")
language.Add("tool.ragmover_ikchains.1","Now left click again to select foot bone.")
language.Add("tool.ragmover_ikchains.0","Left click to select IK hip/upperarm bone.")
language.Add("tool.ragmover_ikchains.1","Now left click again to select foot/hand bone.")
function TOOL.BuildCPanel(CPanel)
CPanel:AddControl("Header",{Name = "#Tool_ragmover_ikchains_name","#Tool_ragmover_ikchains_desc"})
/*local mc = CPanel:MultiChoice("IK chain type","ragmover_ikchains_type")
--[[ local mc = CPanel:MultiChoice("IK chain type","ragmover_ikchains_type")
mc:AddChoice("Left Leg")
mc:AddChoice("Right Leg")
mc:AddChoice("Left Hand")
mc:AddChoice("Right Hand")*/
mc:AddChoice("Left Arm")
mc:AddChoice("Right Arm") ]]
local s = CPanel:NumSlider("IK slot: "..ikchains_iktypes[1],"ragmover_ikchains_type",1,4,0)
s:SetValue(0)
s:SetDecimals(0);
s.ValueChanged = function(self,val)
self:SetText("IK slot: "..ikchains_iktypes[math.ceil(self:GetValue())])
RunConsoleCommand("ragmover_ikchains_type",math.Round(self:GetValue()))
self:SetText("IK slot: "..ikchains_iktypes[math.Round(self:GetValue())])
end
end