ACF_CustomMod/lua/entities/acf_gearbox.lua

807 lines
23 KiB
Lua

AddCSLuaFile()
DEFINE_BASECLASS( "base_wire_entity" )
ENT.PrintName = "ACF Gearbox"
ENT.WireDebugName = "ACF Gearbox"
if CLIENT then
function ACFGearboxGUICreate( Table )
if not acfmenupanel.GearboxData then
acfmenupanel.GearboxData = {}
end
if not acfmenupanel.GearboxData[Table.id] then
acfmenupanel.GearboxData[Table.id] = {}
acfmenupanel.GearboxData[Table.id].GearTable = Table.geartable
end
acfmenupanel:CPanelText("Name", Table.name)
acfmenupanel.CData.DisplayModel = vgui.Create( "DModelPanel", acfmenupanel.CustomDisplay )
acfmenupanel.CData.DisplayModel:SetModel( Table.model )
acfmenupanel.CData.DisplayModel:SetCamPos( Vector( 250, 500, 250 ) )
acfmenupanel.CData.DisplayModel:SetLookAt( Vector( 0, 0, 0 ) )
acfmenupanel.CData.DisplayModel:SetFOV( 20 )
acfmenupanel.CData.DisplayModel:SetSize(acfmenupanel:GetWide(),acfmenupanel:GetWide())
acfmenupanel.CData.DisplayModel.LayoutEntity = function( panel, entity ) end
acfmenupanel.CustomDisplay:AddItem( acfmenupanel.CData.DisplayModel )
acfmenupanel:CPanelText("Desc", Table.desc) --Description (Name, Desc)
if (acfmenupanel.GearboxData[Table.id].GearTable[-2] or 0) != 0 then
ACF_GearsSlider(2, acfmenupanel.GearboxData[Table.id].GearTable[2], Table.id)
ACF_GearsSlider(3, acfmenupanel.GearboxData[Table.id].GearTable[-3], Table.id, "Min Target RPM",true)
ACF_GearsSlider(4, acfmenupanel.GearboxData[Table.id].GearTable[-2], Table.id, "Max Target RPM",true)
ACF_GearsSlider(10, acfmenupanel.GearboxData[Table.id].GearTable[-1], Table.id, "Final Drive")
RunConsoleCommand( "acfmenu_data1", 0.01 )
else
for ID,Value in pairs(acfmenupanel.GearboxData[Table.id].GearTable) do
if ID > 0 then
ACF_GearsSlider(ID, Value, Table.id)
elseif ID == -1 then
ACF_GearsSlider(10, Value, Table.id, "Final Drive")
end
end
end
acfmenupanel:CPanelText("Desc", Table.desc)
acfmenupanel:CPanelText("MaxTorque", "Clutch Maximum Torque Rating : "..(Table.maxtq).."n-m / "..math.Round(Table.maxtq*0.73).."ft-lb")
acfmenupanel:CPanelText("Weight", "Weight : "..Table.weight.."kg")
acfmenupanel.CustomDisplay:PerformLayout()
maxtorque = Table.maxtq
end
function ACF_GearsSlider(Gear, Value, ID, Desc, CVT)
if Gear and not acfmenupanel.CData[Gear] then
acfmenupanel.CData[Gear] = vgui.Create( "DNumSlider", acfmenupanel.CustomDisplay )
acfmenupanel.CData[Gear]:SetText( Desc or "Gear "..Gear )
acfmenupanel.CData[Gear].Label:SizeToContents()
acfmenupanel.CData[Gear]:SetDark( true )
acfmenupanel.CData[Gear]:SetMin( CVT and 1 or -1 )
acfmenupanel.CData[Gear]:SetMax( CVT and 10000 or 1 )
acfmenupanel.CData[Gear]:SetDecimals( (not CVT) and 2 or 0 )
acfmenupanel.CData[Gear].Gear = Gear
acfmenupanel.CData[Gear].ID = ID
acfmenupanel.CData[Gear]:SetValue(Value)
RunConsoleCommand( "acfmenu_data"..Gear, Value )
acfmenupanel.CData[Gear].OnValueChanged = function( slider, val )
acfmenupanel.GearboxData[slider.ID].GearTable[slider.Gear] = val
RunConsoleCommand( "acfmenu_data"..Gear, val )
end
acfmenupanel.CustomDisplay:AddItem( acfmenupanel.CData[Gear] )
end
end
return
end
function ENT:Initialize()
self.IsGeartrain = true
self.Master = {}
self.IsMaster = true
self.WheelLink = {} -- a "Link" has these components: Ent, Side, Axis, Rope, RopeLen, Output, ReqTq, Vel
self.TotalReqTq = 0
self.RClutch = 0
self.LClutch = 0
self.LBrake = 0
self.RBrake = 0
self.SteerRate = 0
self.Gear = 1
self.GearRatio = 0
self.ChangeFinished = 0
self.LegalThink = 0
self.RPM = {}
self.CurRPM = 0
self.CVT = false
self.DoubleDiff = false
self.InGear = false
self.CanUpdate = true
self.LastActive = 0
self.Legal = true
end
function MakeACF_Gearbox(Owner, Pos, Angle, Id, Data1, Data2, Data3, Data4, Data5, Data6, Data7, Data8, Data9, Data10)
if not Owner:CheckLimit("_acf_misc") then return false end
local Gearbox = ents.Create("acf_gearbox")
local List = list.Get("ACFEnts")
local Classes = list.Get("ACFClasses")
if not IsValid( Gearbox ) then return false end
Gearbox:SetAngles(Angle)
Gearbox:SetPos(Pos)
Gearbox:Spawn()
Gearbox:SetPlayer(Owner)
Gearbox.Owner = Owner
Gearbox.Id = Id
Gearbox.Model = List.Mobility[Id].model
Gearbox.Mass = List.Mobility[Id].weight
Gearbox.SwitchTime = List.Mobility[Id].switch
Gearbox.MaxTorque = List.Mobility[Id].maxtq
Gearbox.Gears = List.Mobility[Id].gears
Gearbox.Dual = List.Mobility[Id].doubleclutch
Gearbox.CVT = List.Mobility[Id].cvt
Gearbox.DoubleDiff = List.Mobility[Id].doublediff
if Gearbox.CVT then
Gearbox.TargetMinRPM = Data3
Gearbox.TargetMaxRPM = math.max(Data4,Data3+100)
Gearbox.CVTRatio = nil
end
Gearbox.GearTable = List.Mobility[Id].geartable
Gearbox.GearTable.Final = Data10
Gearbox.GearTable[1] = Data1
Gearbox.GearTable[2] = Data2
Gearbox.GearTable[3] = Data3
Gearbox.GearTable[4] = Data4
Gearbox.GearTable[5] = Data5
Gearbox.GearTable[6] = Data6
Gearbox.GearTable[7] = Data7
Gearbox.GearTable[8] = Data8
Gearbox.GearTable[9] = Data9
Gearbox.GearTable[0] = 0
Gearbox.Gear0 = Data10
Gearbox.Gear1 = Data1
Gearbox.Gear2 = Data2
Gearbox.Gear3 = Data3
Gearbox.Gear4 = Data4
Gearbox.Gear5 = Data5
Gearbox.Gear6 = Data6
Gearbox.Gear7 = Data7
Gearbox.Gear8 = Data8
Gearbox.Gear9 = Data9
Gearbox:SetModel( Gearbox.Model )
local Inputs = {"Gear","Gear Up","Gear Down"}
if Gearbox.CVT then
table.insert(Inputs,"CVT Ratio")
elseif Gearbox.DoubleDiff then
table.insert(Inputs, "Steer Rate")
end
if Gearbox.Dual then
table.insert(Inputs, "Left Clutch")
table.insert(Inputs, "Right Clutch")
table.insert(Inputs, "Left Brake")
table.insert(Inputs, "Right Brake")
else
table.insert(Inputs, "Clutch")
table.insert(Inputs, "Brake")
end
local Outputs = { "Ratio", "Entity", "Current Gear", "Gearbox RPM" }
local OutputTypes = { "NORMAL", "ENTITY", "NORMAL", "NORMAL" }
if Gearbox.CVT then
table.insert(Outputs,"Min Target RPM")
table.insert(Outputs,"Max Target RPM")
table.insert(OutputTypes,"NORMAL")
end
Gearbox.Inputs = Wire_CreateInputs( Gearbox, Inputs )
Gearbox.Outputs = WireLib.CreateSpecialOutputs( Gearbox, Outputs, OutputTypes )
Wire_TriggerOutput(Gearbox, "Entity", Gearbox)
if Gearbox.CVT then
Wire_TriggerOutput(Gearbox, "Min Target RPM", Gearbox.TargetMinRPM)
Wire_TriggerOutput(Gearbox, "Max Target RPM", Gearbox.TargetMaxRPM)
end
Gearbox.LClutch = Gearbox.MaxTorque
Gearbox.RClutch = Gearbox.MaxTorque
Gearbox:PhysicsInit( SOLID_VPHYSICS )
Gearbox:SetMoveType( MOVETYPE_VPHYSICS )
Gearbox:SetSolid( SOLID_VPHYSICS )
local phys = Gearbox:GetPhysicsObject()
if IsValid( phys ) then
phys:SetMass( Gearbox.Mass )
end
Gearbox.In = Gearbox:WorldToLocal(Gearbox:GetAttachment(Gearbox:LookupAttachment( "input" )).Pos)
Gearbox.OutL = Gearbox:WorldToLocal(Gearbox:GetAttachment(Gearbox:LookupAttachment( "driveshaftL" )).Pos)
Gearbox.OutR = Gearbox:WorldToLocal(Gearbox:GetAttachment(Gearbox:LookupAttachment( "driveshaftR" )).Pos)
Owner:AddCount("_acf_gearbox", Gearbox)
Owner:AddCleanup( "acfmenu", Gearbox )
Gearbox:ChangeGear(1)
if Gearbox.Dual or Gearbox.DoubleDiff then
Gearbox:SetBodygroup(1, 1)
else
Gearbox:SetBodygroup(1, 0)
end
Gearbox:SetNetworkedString( "WireName", List.Mobility[Id].name )
Gearbox:UpdateOverlayText()
return Gearbox
end
list.Set( "ACFCvars", "acf_gearbox", {"id", "data1", "data2", "data3", "data4", "data5", "data6", "data7", "data8", "data9", "data10"} )
duplicator.RegisterEntityClass("acf_gearbox", MakeACF_Gearbox, "Pos", "Angle", "Id", "Gear1", "Gear2", "Gear3", "Gear4", "Gear5", "Gear6", "Gear7", "Gear8", "Gear9", "Gear0" )
function ENT:Update( ArgsTable )
-- That table is the player data, as sorted in the ACFCvars above, with player who shot,
-- and pos and angle of the tool trace inserted at the start
if ArgsTable[1] ~= self.Owner then -- Argtable[1] is the player that shot the tool
return false, "You don't own that gearbox!"
end
local Id = ArgsTable[4] -- Argtable[4] is the engine ID
local List = list.Get("ACFEnts")
if List.Mobility[Id].model ~= self.Model then
return false, "The new gearbox must have the same model!"
end
if self.Id != Id then
self.Id = Id
self.Mass = List.Mobility[Id].weight
self.SwitchTime = List.Mobility[Id].switch
self.MaxTorque = List.Mobility[Id].maxtq
self.Gears = List.Mobility[Id].gears
self.Dual = List.Mobility[Id].doubleclutch
self.CVT = List.Mobility[Id].cvt
self.DoubleDiff = List.Mobility[Id].doublediff
local Inputs = {"Gear","Gear Up","Gear Down"}
if self.CVT then
table.insert(Inputs,"CVT Ratio")
elseif self.DoubleDiff then
table.insert(Inputs, "Steer Rate")
end
if self.Dual then
table.insert(Inputs, "Left Clutch")
table.insert(Inputs, "Right Clutch")
table.insert(Inputs, "Left Brake")
table.insert(Inputs, "Right Brake")
else
table.insert(Inputs, "Clutch")
table.insert(Inputs, "Brake")
end
local Outputs = { "Ratio", "Entity", "Current Gear" }
local OutputTypes = { "NORMAL", "ENTITY", "NORMAL" }
if self.CVT then
table.insert(Outputs,"Min Target RPM")
table.insert(Outputs,"Max Target RPM")
table.insert(OutputTypes,"NORMAL")
end
local phys = self:GetPhysicsObject()
if IsValid( phys ) then
phys:SetMass( self.Mass )
end
self.Inputs = Wire_CreateInputs( self, Inputs )
self.Outputs = WireLib.CreateSpecialOutputs( self, Outputs, OutputTypes )
Wire_TriggerOutput( self, "Entity", self )
end
if self.CVT then
self.TargetMinRPM = ArgsTable[7]
self.TargetMaxRPM = math.max(ArgsTable[8],ArgsTable[7]+100)
self.CVTRatio = nil
Wire_TriggerOutput(self, "Min Target RPM", self.TargetMinRPM)
Wire_TriggerOutput(self, "Max Target RPM", self.TargetMaxRPM)
end
self.GearTable.Final = ArgsTable[14]
self.GearTable[1] = ArgsTable[5]
self.GearTable[2] = ArgsTable[6]
self.GearTable[3] = ArgsTable[7]
self.GearTable[4] = ArgsTable[8]
self.GearTable[5] = ArgsTable[9]
self.GearTable[6] = ArgsTable[10]
self.GearTable[7] = ArgsTable[11]
self.GearTable[8] = ArgsTable[12]
self.GearTable[9] = ArgsTable[13]
self.GearTable[0] = 0
self.Gear0 = ArgsTable[14]
self.Gear1 = ArgsTable[5]
self.Gear2 = ArgsTable[6]
self.Gear3 = ArgsTable[7]
self.Gear4 = ArgsTable[8]
self.Gear5 = ArgsTable[9]
self.Gear6 = ArgsTable[10]
self.Gear7 = ArgsTable[11]
self.Gear8 = ArgsTable[12]
self.Gear9 = ArgsTable[13]
self:ChangeGear(1)
if self.Dual or self.DoubleDiff then
self:SetBodygroup(1, 1)
else
self:SetBodygroup(1, 0)
end
self:SetNetworkedString( "WireName", List.Mobility[Id].name )
self:UpdateOverlayText()
return true, "Gearbox updated successfully!"
end
function ENT:UpdateOverlayText()
local text = ""
if self.CVT then
text = "Gear 2: " .. math.Round( self.GearTable[ 2 ], 2 ) -- maybe a better name than "gear 2"...?
text = text .. "\nTarget: " .. math.Round( self.TargetMinRPM ) .. " - " .. math.Round( self.TargetMaxRPM ) .. " RPM\n"
else
for i = 1, self.Gears do
text = text .. "Gear " .. i .. ": " .. math.Round( self.GearTable[ i ], 2 ) .. "\n"
end
end
text = text .. "Final Drive: " .. math.Round( self.Gear0, 2 ) .. "\n"
text = text .. "Torque Rating: " .. self.MaxTorque .. " Nm / " .. math.Round( self.MaxTorque * 0.73 ) .. " ft-lb"
self:SetOverlayText( text )
end
-- prevent people from changing bodygroup
function ENT:CanProperty( ply, property )
return property ~= "bodygroups"
end
function ENT:TriggerInput( iname, value )
if ( iname == "Gear" and self.Gear != math.floor(value) ) then
self:ChangeGear(math.floor(value))
elseif ( iname == "Gear Up" ) then
if ( self.Gear < self.Gears and value > 0 ) then
self:ChangeGear(math.floor(self.Gear + 1))
end
elseif ( iname == "Gear Down" ) then
if ( self.Gear > 1 and value > 0 ) then
self:ChangeGear(math.floor(self.Gear - 1))
end
elseif ( iname == "Clutch" ) then
self.LClutch = math.Clamp(1-value,0,1)*self.MaxTorque
self.RClutch = math.Clamp(1-value,0,1)*self.MaxTorque
elseif ( iname == "Brake" ) then
self.LBrake = math.Clamp(value,0,100)
self.RBrake = math.Clamp(value,0,100)
elseif ( iname == "Left Brake" ) then
self.LBrake = math.Clamp(value,0,100)
elseif ( iname == "Right Brake" ) then
self.RBrake = math.Clamp(value,0,100)
elseif ( iname == "Left Clutch" ) then
self.LClutch = math.Clamp(1-value,0,1)*self.MaxTorque
elseif ( iname == "Right Clutch" ) then
self.RClutch = math.Clamp(1-value,0,1)*self.MaxTorque
elseif ( iname == "CVT Ratio" ) then
self.CVTRatio = math.Clamp(value,0,1)
elseif ( iname == "Steer Rate" ) then
self.SteerRate = math.Clamp(value,-1,1)
end
end
function ENT:Think()
local Time = CurTime()
if self.LastActive + 2 > Time then
self:CheckRopes()
end
self.Legal = self:CheckLegal()
self:NextThink( Time + math.random( 5, 10 ) )
return true
end
function ENT:CheckLegal()
-- make sure weight is not below stock
if self:GetPhysicsObject():GetMass() < self.Mass then return false end
-- if it's not parented we're fine
if not IsValid( self:GetParent() ) then return true end
-- but not if it's parented to a parented prop
if IsValid( self:GetParent():GetParent() ) then return false end
-- parenting is only legal if it's also welded
for k, v in pairs( constraint.FindConstraints( self, "Weld" ) ) do
if v.Ent1 == self:GetParent() or v.Ent2 == self:GetParent() then return true end
end
return false
end
function ENT:CheckRopes()
for Key, Link in pairs( self.WheelLink ) do
local Ent = Link.Ent
-- make sure the rope is still there
if not IsValid( Link.Rope ) then
self:Unlink( Ent )
continue end
local OutPos = self:LocalToWorld( Link.Output )
local InPos = Ent:GetPos()
if Ent.IsGeartrain then
InPos = Ent:LocalToWorld( Ent.In )
end
-- make sure it is not stretched too far
if OutPos:Distance( InPos ) > Link.RopeLen * 1.5 then
self:Unlink( Ent )
continue end
-- make sure the angle is not excessive
local DrvAngle = ( OutPos - InPos ):GetNormalized():DotProduct( ( self:GetRight() * Link.Output.y ):GetNormalized() )
if DrvAngle < 0.7 then
self:Unlink( Ent )
end
end
end
-- Check if every entity we are linked to still actually exists
-- and remove any links that are invalid.
function ENT:CheckEnts()
for Key, Link in pairs( self.WheelLink ) do
if not IsValid( Link.Ent ) then
table.remove( self.WheelLink, Key )
continue end
local Phys = Link.Ent:GetPhysicsObject()
if not IsValid( Phys ) then
Link.Ent:Remove()
table.remove( self.WheelLink, Key )
end
end
end
function ENT:Calc( InputRPM, InputInertia )
if self.LastActive == CurTime() then
return math.min( self.TotalReqTq, self.MaxTorque )
end
if self.ChangeFinished < CurTime() then
self.InGear = true
end
self:CheckEnts()
local BoxPhys = self:GetPhysicsObject()
local SelfWorld = self:LocalToWorld( BoxPhys:GetAngleVelocity() ) - self:GetPos()
self.TotalReqTq = 0
for Key, Link in pairs( self.WheelLink ) do
if not IsValid( Link.Ent ) then
table.remove( self.WheelLink, Key )
continue end
local Clutch = 0
if Link.Side == 0 then
Clutch = self.LClutch
elseif Link.Side == 1 then
Clutch = self.RClutch
end
if self.CVT and self.Gear == 1 then
if self.CVTRatio and self.CVTRatio > 0 then
self.GearTable[1] = math.Clamp(self.CVTRatio,0.01,1)
else
self.GearTable[1] = math.Clamp((InputRPM - self.TargetMinRPM) / ((self.TargetMaxRPM - self.TargetMinRPM) or 1),0.05,1)
end
self.GearRatio = (self.GearTable[1] or 0)*self.GearTable.Final
Wire_TriggerOutput(self, "Ratio", self.GearRatio)
end
Link.ReqTq = 0
if Link.Ent.IsGeartrain then
if not Link.Ent.Legal then continue end
local Inertia = 0
if self.GearRatio ~= 0 then Inertia = InputInertia / self.GearRatio end
Link.ReqTq = math.min( Clutch, math.abs( Link.Ent:Calc( InputRPM * self.GearRatio, Inertia, self.GearRatio ) * self.GearRatio ) )
elseif self.DoubleDiff then
local RPM = self:CalcWheel( Link, SelfWorld )
if self.GearRatio ~= 0 and ( ( InputRPM > 0 and RPM < InputRPM ) or ( InputRPM < 0 and RPM > InputRPM ) ) then
local NTq = math.min( Clutch, ( InputRPM - RPM) * InputInertia)
if( self.SteerRate ~= 0 ) then
Sign = self.SteerRate / math.abs( self.SteerRate )
else
Sign = 0
end
if Link.Side == 0 then
local DTq = math.Clamp( ( self.SteerRate * ( ( InputRPM * ( math.abs( self.SteerRate ) + 1 ) ) - (RPM * Sign) ) ) * InputInertia, -self.MaxTorque, self.MaxTorque )
Link.ReqTq = ( NTq + DTq )
elseif Link.Side == 1 then
local DTq = math.Clamp( ( self.SteerRate * ( ( InputRPM * ( math.abs( self.SteerRate ) + 1 ) ) + (RPM * Sign) ) ) * InputInertia, -self.MaxTorque, self.MaxTorque )
Link.ReqTq = ( NTq - DTq )
end
end
else
local RPM = self:CalcWheel( Link, SelfWorld )
if self.GearRatio ~= 0 and ( ( InputRPM > 0 and RPM < InputRPM ) or ( InputRPM < 0 and RPM > InputRPM ) ) then
Link.ReqTq = math.min( Clutch, ( InputRPM - RPM ) * InputInertia )
end
--Calling RPM Ouputs Value's
if Clutch == 0 then
Wire_TriggerOutput(self, "Gearbox RPM", 0)
elseif Clutch > 0 then
Wire_TriggerOutput(self, "Gearbox RPM", RPM)
end
end
self.TotalReqTq = self.TotalReqTq + math.abs( Link.ReqTq )
end
return math.min( self.TotalReqTq, self.MaxTorque )
end
function ENT:CalcWheel( Link, SelfWorld )
local Wheel = Link.Ent
local WheelPhys = Wheel:GetPhysicsObject()
local VelDiff = ( Wheel:LocalToWorld( WheelPhys:GetAngleVelocity() ) - Wheel:GetPos() ) - SelfWorld
local BaseRPM = VelDiff:Dot( Wheel:LocalToWorld( Link.Axis ) - Wheel:GetPos() )
Link.Vel = BaseRPM
if self.GearRatio == 0 then return 0 end
-- Reported BaseRPM is in angle per second and in the wrong direction, so we convert and add the gearratio
return BaseRPM / self.GearRatio / -6
end
function ENT:Act( Torque, DeltaTime )
local ReactTq = 0
-- Calculate the ratio of total requested torque versus what's avaliable, and then multiply it but the current gearratio
local AvailTq = 0
if Torque ~= 0 then
AvailTq = math.min( math.abs( Torque ) / self.TotalReqTq, 1 ) / self.GearRatio * -( -Torque / math.abs( Torque ) )
end
for Key, Link in pairs( self.WheelLink ) do
local Brake = 0
if Link.Side == 0 then
Brake = self.LBrake
elseif Link.Side == 1 then
Brake = self.RBrake
end
if Link.Ent.IsGeartrain then
Link.Ent:Act( Link.ReqTq * AvailTq, DeltaTime )
else
self:ActWheel( Link, Link.ReqTq * AvailTq, Brake, DeltaTime )
ReactTq = ReactTq + Link.ReqTq * AvailTq
end
end
local BoxPhys = self:GetPhysicsObject()
if IsValid( BoxPhys ) and ReactTq ~= 0 then
local Force = self:GetForward() * ReactTq - self:GetForward()
BoxPhys:ApplyForceOffset( Force * 39.37 * DeltaTime, self:GetPos() + self:GetUp() * -39.37 )
BoxPhys:ApplyForceOffset( Force * -39.37 * DeltaTime, self:GetPos() + self:GetUp() * 39.37 )
end
self.LastActive = CurTime()
end
function ENT:ActWheel( Link, Torque, Brake, DeltaTime )
local Phys = Link.Ent:GetPhysicsObject()
local Pos = Link.Ent:GetPos()
local TorqueAxis = Link.Ent:LocalToWorld( Link.Axis ) - Pos
local Cross = TorqueAxis:Cross( Vector( TorqueAxis.y, TorqueAxis.z, TorqueAxis.x ) )
local TorqueVec = TorqueAxis:Cross( Cross ):GetNormalized()
local BrakeMult = 0
if Brake > 0 then
BrakeMult = Link.Vel * Phys:GetInertia() * Brake / 10
end
local Force = TorqueVec * Torque * 0.75 + TorqueVec * BrakeMult
Phys:ApplyForceOffset( Force * -39.37 * DeltaTime, Pos + Cross * 39.37 )
Phys:ApplyForceOffset( Force * 39.37 * DeltaTime, Pos + Cross * -39.37 )
end
function ENT:ChangeGear(value)
self.Gear = math.Clamp(value,0,self.Gears)
self.GearRatio = (self.GearTable[self.Gear] or 0)*self.GearTable.Final
self.ChangeFinished = CurTime() + self.SwitchTime
self.InGear = false
Wire_TriggerOutput(self, "Current Gear", self.Gear)
self:EmitSound("buttons/lever7.wav",250,100)
Wire_TriggerOutput(self, "Ratio", self.GearRatio)
end
function ENT:Link( Target )
if not IsValid( Target ) or not table.HasValue( { "prop_physics", "acf_gearbox", "tire", "acf_gearboxcvt", "acf_gearboxair", "acf_gearboxauto" }, Target:GetClass() ) then
return false, "Can only link props or gearboxes!"
end
-- Check if target is already linked
for Key, Link in pairs( self.WheelLink ) do
if Link.Ent == Target then
return false, "That is already linked to this gearbox!"
end
end
-- make sure the angle is not excessive
local InPos = Vector( 0, 0, 0 )
if Target.IsGeartrain then
InPos = Target.In
end
local InPosWorld = Target:LocalToWorld( InPos )
local OutPos = self.OutR
local Side = 1
if self:WorldToLocal( InPosWorld ).y < 0 then
OutPos = self.OutL
Side = 0
end
local OutPosWorld = self:LocalToWorld( OutPos )
local DrvAngle = ( OutPosWorld - InPosWorld ):GetNormalized():DotProduct( ( self:GetRight() * OutPos.y ):GetNormalized() )
if DrvAngle < 0.7 then
return false, "Cannot link due to excessive driveshaft angle!"
end
local Rope = constraint.CreateKeyframeRope( OutPosWorld, 1, "cable/cable2", nil, self, OutPos, 0, Target, InPos, 0 )
local Link = {
Ent = Target,
Side = Side,
Axis = Target:WorldToLocal( self:GetRight() + InPosWorld ),
Rope = Rope,
RopeLen = ( OutPosWorld - InPosWorld ):Length(),
Output = OutPos,
ReqTq = 0,
Vel = 0
}
table.insert( self.WheelLink, Link )
return true, "Link successful!"
end
function ENT:Unlink( Target )
for Key, Link in pairs( self.WheelLink ) do
if Link.Ent == Target then
-- Remove any old physical ropes leftover from dupes
for Key, Rope in pairs( constraint.FindConstraints( Link.Ent, "Rope" ) ) do
if Rope.Ent1 == self or Rope.Ent2 == self then
Rope.Constraint:Remove()
end
end
if IsValid( Link.Rope ) then
Link.Rope:Remove()
end
table.remove( self.WheelLink, Key )
return true, "Unlink successful!"
end
end
return false, "That entity is not linked to this gearbox!"
end
function ENT:PreEntityCopy()
-- Link Saving
local info = {}
local entids = {}
-- Clean the table of any invalid entities
for Key, Link in pairs( self.WheelLink ) do
if not IsValid( Link.Ent ) then
table.remove( self.WheelLink, Key )
end
end
-- Then save it
for Key, Link in pairs( self.WheelLink ) do
table.insert( entids, Link.Ent:EntIndex() )
end
info.entities = entids
if info.entities then
duplicator.StoreEntityModifier( self, "WheelLink", info )
end
//Wire dupe info
self.BaseClass.PreEntityCopy( self )
end
function ENT:PostEntityPaste( Player, Ent, CreatedEntities )
-- Link Pasting
if Ent.EntityMods and Ent.EntityMods.WheelLink and Ent.EntityMods.WheelLink.entities then
local WheelLink = Ent.EntityMods.WheelLink
if WheelLink.entities and table.Count( WheelLink.entities ) > 0 then
timer.Simple( 0, function() -- this timer is a workaround for an ad2/makespherical issue https://github.com/nrlulz/ACF/issues/14#issuecomment-22844064
for _, ID in pairs( WheelLink.entities ) do
local Linked = CreatedEntities[ ID ]
if IsValid( Linked ) then
self:Link( Linked )
end
end
end )
end
Ent.EntityMods.WheelLink = nil
end
//Wire dupe info
self.BaseClass.PostEntityPaste( self, Player, Ent, CreatedEntities )
end
function ENT:OnRemove()
for Key,Value in pairs(self.Master) do --Let's unlink ourselves from the engines properly
if IsValid( self.Master[Key] ) then
self.Master[Key]:Unlink( self )
end
end
end