diff --git a/lua/acf/core/utilities/util_cl.lua b/lua/acf/core/utilities/util_cl.lua index 1aa17a723..62e6a7928 100644 --- a/lua/acf/core/utilities/util_cl.lua +++ b/lua/acf/core/utilities/util_cl.lua @@ -462,6 +462,7 @@ end do -- Default turret menus local Turrets = ACF.Classes.Turrets + local TurretMassText = "Drive Mass : %s kg, %s kg max capacity" local MassText = "Mass : %s kg" do -- Turret ring @@ -483,6 +484,7 @@ do -- Default turret menus TurretClass = Data.ID, Teeth = TurretClass.GetTeethCount(Data,Data.Size.Base), TotalMass = 0, + MaxMass = 0, RingSize = Data.Size.Base, RingHeight = TurretClass.GetRingHeight({Type = "Turret-H",Ratio = Data.Size.Ratio},Data.Size.Base), LocalCoM = Vector(), @@ -491,13 +493,17 @@ do -- Default turret menus local RingSize = Menu:AddSlider("Ring Diameter", Data.Size.Min, Data.Size.Max, 2) - local RingStats = Menu:AddLabel(TurretText:format(0,0)) - local MassLbl = Menu:AddLabel(MassText:format(0)) + local MaxSpeed = Menu:AddSlider("Max Speed (deg/s)", 0, 120, 2) + + Menu:AddLabel("If the Max Speed slider is lower than the calculated max speed of the turret, this will be the new limit. If 0, it will default to the actual max speed.") - Menu:AddLabel("If the total arc is less than 360, then it will use the limits set here.\nIf it is 360, then it will have free rotation.") + local RingStats = Menu:AddLabel(TurretText:format(0,0)) + local MassLbl = Menu:AddLabel(MassText:format(0,0)) local ArcSettings = Menu:AddCollapsible("Arc Settings") + ArcSettings:AddLabel("If the total arc is less than 360, then it will use the limits set here.\nIf it is 360, then it will have free rotation.") + local MinDeg = ArcSettings:AddSlider("Minimum Degrees", -180, 0, 1) local MaxDeg = ArcSettings:AddSlider("Maximum Degrees", 0, 180, 1) @@ -617,7 +623,8 @@ do -- Default turret menus Teeth = TurretData.Teeth, Tilt = 1, TurretClass = TurretData.TurretClass, - TotalMass = 0 + TotalMass = 0, + MaxMass = TurretData.MaxMass, } local Points = {} @@ -645,19 +652,31 @@ do -- Default turret menus local Teeth = TurretClass.GetTeethCount(Data,N) RingStats:SetText(TurretText:format(Teeth)) - MassLbl:SetText(MassText:format(TurretClass.GetMass(Data,N))) + local MaxMass = TurretClass.GetMaxMass(Data,N) + MassLbl:SetText(TurretMassText:format(TurretClass.GetMass(Data,N), MaxMass)) TurretData.Teeth = Teeth TurretData.RingSize = N TurretData.RingHeight = TurretClass.GetRingHeight({Type = Data.ID,Ratio = Data.Size.Ratio},N) + TurretData.MaxMass = MaxMass EstDist:SetMinMax(0,math.max(N * 2,24)) + MaxSpeed:SetValue(0) HandCrankLbl:UpdateSim() return N end) + MaxSpeed:SetClientData("MaxSpeed", "OnValueChanged") + MaxSpeed:DefineSetter(function(Panel, _, _, Value) + local N = Value + + Panel:SetValue(N) + + return N + end) + EstMass.OnValueChanged = function(_, Value) TurretData.TotalMass = Value @@ -673,6 +692,7 @@ do -- Default turret menus RingSize:SetValue(Data.Size.Base) EstMass:SetValue(0) EstDist:SetValue(0) + MaxSpeed:SetValue(0) TurretData.Ready = true HandCrankLbl:UpdateSim() @@ -711,7 +731,7 @@ do -- Default turret menus Menu:AddLabel("Determines the number of teeth of the gear on the motor.") local TeethAmt = Menu:AddSlider("Gear Teeth (" .. Data.Teeth.Min .. "-" .. Data.Teeth.Max .. ")", Data.Teeth.Min, Data.Teeth.Max, 0) - local MassLbl = Menu:AddLabel(MassText:format(0)) + local MassLbl = Menu:AddLabel(TurretMassText:format(0,0)) local TorqLbl = Menu:AddLabel(TorqText:format(0)) -- Simulation @@ -727,6 +747,8 @@ do -- Default turret menus local EstDist = TurretSim:AddSlider("Mass Center Dist.", 0, 2, 2) + local MaxMassLbl = TurretSim:AddLabel("Max mass: 0kg") + local Graph = Menu:AddGraph() local GraphSize = Menu:GetParent():GetParent():GetWide() Graph:SetSize(GraphSize, GraphSize / 2) @@ -746,7 +768,8 @@ do -- Default turret menus Teeth = TurretData.TurretTeeth, Tilt = 1, TurretClass = TurretData.Type, - TotalMass = 0 + TotalMass = 0, + MaxMass = TurretData.MaxMass, } local SimMotorData = { @@ -781,7 +804,7 @@ do -- Default turret menus HandcrankInfo.UpdateSim = function(Panel) if TurretData.Ready == false then return end - local Info = TurretClass.CalcSpeed({Tilt = 1, TotalMass = TurretData.Mass, RingSize = TurretData.Size, Teeth = TurretData.TurretTeeth, TurretClass = TurretData.Type, LocalCoM = Vector(TurretData.Distance,0,TurretData.Distance), RingHeight = TurretData.RingHeight}, + local Info = TurretClass.CalcSpeed({Tilt = 1, TotalMass = TurretData.Mass, MaxMass = TurretData.MaxMass, RingSize = TurretData.Size, Teeth = TurretData.TurretTeeth, TurretClass = TurretData.Type, LocalCoM = Vector(TurretData.Distance,0,TurretData.Distance), RingHeight = TurretData.RingHeight}, TurretClass.HandGear) Panel:SetText(HandcrankText:format(math.Round(Info.MaxSlewRate, 2), math.Round(Info.SlewAccel, 4))) @@ -794,7 +817,7 @@ do -- Default turret menus MotorInfo.UpdateSim = function(Panel) if TurretData.Ready == false then return end - local Info = TurretClass.CalcSpeed({Tilt = 1, TotalMass = TurretData.Mass, RingSize = TurretData.Size, Teeth = TurretData.TurretTeeth, TurretClass = TurretData.Type, LocalCoM = Vector(TurretData.Distance,0,TurretData.Distance), RingHeight = TurretData.RingHeight}, + local Info = TurretClass.CalcSpeed({Tilt = 1, TotalMass = TurretData.Mass, MaxMass = TurretData.MaxMass, RingSize = TurretData.Size, Teeth = TurretData.TurretTeeth, TurretClass = TurretData.Type, LocalCoM = Vector(TurretData.Distance,0,TurretData.Distance), RingHeight = TurretData.RingHeight}, {Teeth = TurretData.MotorTeeth, Speed = Data.Speed, Torque = TurretData.Torque, Efficiency = Data.Efficiency, Accel = Data.Accel}) Panel:SetText(MotorText:format(math.Round(Info.MaxSlewRate, 2), math.Round(Info.SlewAccel, 4))) @@ -841,8 +864,10 @@ do -- Default turret menus TurretData.Size = Value TurretData.RingHeight = TurretClass.GetRingHeight({Type = TurretData.Turret, Ratio = TurretData.Turret.Size.Ratio},Value) TurretData.TurretTeeth = TurretClass.GetTeethCount(TurretData.Turret,Value) + TurretData.MaxMass = TurretClass.GetMaxMass(TurretData.Turret,Value) EstDist:SetMinMax(0,math.max(Value * 2,24)) + MaxMassLbl:SetText("Max mass: " .. math.Round(TurretData.MaxMass,1) .. "kg") MotorInfo:UpdateSim() HandcrankInfo:UpdateSim() diff --git a/lua/acf/entities/turrets/turrets.lua b/lua/acf/entities/turrets/turrets.lua index 531bd7c13..bff7e173d 100644 --- a/lua/acf/entities/turrets/turrets.lua +++ b/lua/acf/entities/turrets/turrets.lua @@ -37,6 +37,10 @@ do -- Turret drives GetMass = function(Data, Size) return math.Round(math.max(Data.Mass * (Size / Data.Size.Base),5) ^ 1.5, 1) end, + GetMaxMass = function(Data, Size) + local SizePerc = (Size - Data.Size.Min) / (Data.Size.Max - Data.Size.Min) + return math.Round(((Data.MassLimit.Min * (1 - SizePerc)) + (Data.MassLimit.Max * SizePerc)) ^ 2, 1) + end, GetTeethCount = function(Data, Size) local SizePerc = (Size - Data.Size.Min) / (Data.Size.Max - Data.Size.Min) return math.Round((Data.Teeth.Min * (1 - SizePerc)) + (Data.Teeth.Max * SizePerc)) @@ -85,6 +89,11 @@ do -- Turret drives local Diameter = (TurretData.RingSize * InchToMm) -- Used for some of the formulas from the referenced page, needs to be in mm local CoMDistance = (TurretData.LocalCoM * Vector(1,1,0)):Length() * (InchToMm / 1000) -- (Lateral) Distance of center of mass from center of axis, in meters for calculation local OffBaseDistance = math.max(CoMDistance - math.max(CoMDistance - (Diameter / 2),0),0) + local OverweightMod = 1 + + if TurretData.TotalMass > TurretData.MaxMass then + OverweightMod = 1 - (((TurretData.TotalMass - TurretData.MaxMass) / TurretData.MaxMass) / 2) + end -- Slewing ring friction moment caused by load (kNm) -- 1kg weight (mass * gravity) is about 9.81N @@ -112,7 +121,7 @@ do -- Turret drives -- 9.55 is 1 rad/s to RPM -- Required power to rotate at full speed -- With this we can lower maximum attainable speed - local ReqConstantPower = (Mz * TopSpeed) / (9.55 * PowerData.Efficiency) + local ReqConstantPower = (Mz * TopSpeed) / (9.55 * PowerData.Efficiency * OverweightMod) if (math.max(1,ReqConstantPower) / math.max(MaxPower,1)) > 1 then return {SlewAccel = 0, MaxSlewRate = 0} end -- Too heavy to rotate, so we'll just stop here @@ -157,8 +166,13 @@ do -- Turret drives }, Armor = { - Min = 15, - Max = 175 + Min = 50, + Max = 300 + }, + + MassLimit = { + Min = 12, + Max = 960 }, SetupInputs = function(_,List) @@ -224,8 +238,13 @@ do -- Turret drives }, Armor = { - Min = 5, - Max = 305 + Min = 50, + Max = 300 + }, + + MassLimit = { + Min = 16, + Max = 256 }, SetupInputs = function(_,List) diff --git a/lua/acf/entities/weapons/howitzer.lua b/lua/acf/entities/weapons/howitzer.lua index ae5814cf0..81f1231b5 100644 --- a/lua/acf/entities/weapons/howitzer.lua +++ b/lua/acf/entities/weapons/howitzer.lua @@ -4,7 +4,7 @@ local Weapons = ACF.Classes.Weapons Weapons.Register("HW", { Name = "Howitzer", - Description = "Analog of cannons, except it's intended to fire explosive and chemical rounds where its bigger round size a exceels at.", + Description = "Analog of cannons, except it's intended to fire explosive and chemical rounds where the bigger round size excels.", Sound = "acf_base/weapons/howitzer_new2.mp3", Model = "models/howitzer/howitzer_105mm.mdl", MuzzleFlash = "howie_muzzleflash_noscale", diff --git a/lua/entities/acf_engine/cl_init.lua b/lua/entities/acf_engine/cl_init.lua index 6f1404466..50b951a83 100644 --- a/lua/entities/acf_engine/cl_init.lua +++ b/lua/entities/acf_engine/cl_init.lua @@ -1,3 +1,7 @@ +local ACF = ACF +local Clock = ACF.Utilities.Clock +local Queued = {} + include("shared.lua") language.Add("Cleanup_acf_engine", "ACF Engines") @@ -7,3 +11,113 @@ language.Add("SBoxLimit__acf_engine", "You've reached the ACF Engines limit!") function ENT:Update() self.HitBoxes = ACF.GetHitboxes(self:GetModel()) end + +do -- NET SURFER 2.0 + net.Receive("ACF_InvalidateEngineInfo",function() + local Engine = net.ReadEntity() + + if not IsValid(Engine) then return end + + Engine.HasData = false + end) + + net.Receive("ACF_RequestEngineInfo",function() + local Engine = net.ReadEntity() + local Data = util.JSONToTable(net.ReadString()) + local Outputs = util.JSONToTable(net.ReadString()) + + local OutEnts = {} + + for _,E in ipairs(Outputs) do + local Ent = Entity(E) + local Pos = Ent:WorldToLocal(Ent:GetAttachment(Ent:LookupAttachment("input")).Pos) + + OutEnts[#OutEnts + 1] = {Ent = Ent, Pos = Pos} + end + + Engine.Outputs = OutEnts + + Engine.Driveshaft = Data.Driveshaft + + Engine.HasData = true + Engine.Age = Clock.CurTime + 5 + + Queued[Engine] = nil + end) + + function ENT:RequestEngineInfo() + if Queued[self] then return end + + Queued[self] = true + + timer.Simple(5, function() Queued[self] = nil end) + + net.Start("ACF_RequestEngineInfo") + net.WriteEntity(self) + net.SendToServer() + end +end + +do -- Overlay + -- Rendered is used to prevent re-rendering as part of the extended link rendering + + local red = Color(255,0,0) + local orange = Color(255,127,0) + function ENT:DrawLinks(Rendered) + if Rendered[self] then return end + local SelfTbl = self:GetTable() + + Rendered[self] = true + + if not SelfTbl.HasData then + self:RequestEngineInfo() + return + elseif Clock.CurTime > SelfTbl.Age then + self:RequestEngineInfo() + end + + -- draw links to gearboxes + local Perc = (Clock.CurTime / 2) % 1 + + local OutPos = self:LocalToWorld(SelfTbl.Driveshaft) + for _,T in ipairs(SelfTbl.Outputs) do + local E = T.Ent + + if IsValid(E) then + + local Pos = E:LocalToWorld(T.Pos) + --render.DrawLine(OutPos, Pos, color_white, true) + render.DrawBeam(OutPos, Pos, 2, 0, 0, color_black) + render.DrawBeam(OutPos, Pos, 1.5, 0, 0, color_white) + local SpherePos = LerpVector(Perc, OutPos, Pos) + render.DrawSphere(SpherePos, 2, 4, 3, orange) + + if E.DrawLinks then + E:DrawLinks(Rendered,false) + end + end + end + + render.DrawSphere(OutPos, 2, 4, 3, red) + end + + function ENT:DrawOverlay() + local SelfTbl = self:GetTable() + + if not SelfTbl.HasData then + self:RequestEngineInfo() + return + elseif Clock.CurTime > SelfTbl.Age then + self:RequestEngineInfo() + end + + render.SetColorMaterial() + + self:DrawLinks({self = true}, true) + + local OutTextPos = self:LocalToWorld(SelfTbl.Driveshaft):ToScreen() + cam.Start2D() + draw.SimpleTextOutlined("Output","ACF_Title",OutTextPos.x,OutTextPos.y,color_white,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER,1,color_black) + cam.End2D() + end +end \ No newline at end of file diff --git a/lua/entities/acf_engine/init.lua b/lua/entities/acf_engine/init.lua index 1bfa7ef8b..56c0343c6 100644 --- a/lua/entities/acf_engine/init.lua +++ b/lua/entities/acf_engine/init.lua @@ -80,6 +80,8 @@ do Engine:UpdateOverlay() Target:UpdateOverlay() + Engine:InvalidateClientInfo() + return true, "Engine linked successfully!" end) @@ -98,6 +100,8 @@ do Engine:UpdateOverlay() Target:UpdateOverlay() + Engine:InvalidateClientInfo() + return true, "Engine unlinked successfully!" end) end @@ -900,4 +904,38 @@ function ENT:OnRemove() TimerRemove("ACF Engine Clock " .. self:EntIndex()) WireLib.Remove(self) +end + +do -- NET SURFER 2.0 + util.AddNetworkString("ACF_RequestEngineInfo") + util.AddNetworkString("ACF_InvalidateEngineInfo") + + function ENT:InvalidateClientInfo() + net.Start("ACF_InvalidateEngineInfo") + net.WriteEntity(self) + net.Broadcast() + end + + net.Receive("ACF_RequestEngineInfo",function(_,Ply) + local Entity = net.ReadEntity() + + if IsValid(Entity) then + local Outputs = {} + local Data = { + Driveshaft = Entity.Out + } + + if next(Entity.Gearboxes) then + for E in pairs(Entity.Gearboxes) do + Outputs[#Outputs + 1] = E:EntIndex() + end + end + + net.Start("ACF_RequestEngineInfo") + net.WriteEntity(Entity) + net.WriteString(util.TableToJSON(Data)) + net.WriteString(util.TableToJSON(Outputs)) + net.Send(Ply) + end + end) end \ No newline at end of file diff --git a/lua/entities/acf_gearbox/cl_init.lua b/lua/entities/acf_gearbox/cl_init.lua index 1ef5cc687..5f555bc97 100644 --- a/lua/entities/acf_gearbox/cl_init.lua +++ b/lua/entities/acf_gearbox/cl_init.lua @@ -1,3 +1,7 @@ +local ACF = ACF +local Clock = ACF.Utilities.Clock +local Queued = {} + include("shared.lua") language.Add("Cleanup_acf_gearbox", "ACF Gearboxes") @@ -7,3 +11,191 @@ language.Add("SBoxLimit__acf_gearbox", "You've reached the ACF Gearboxes limit!" function ENT:Update() self.HitBoxes = ACF.GetHitboxes(self:GetModel()) end + +do -- NET SURFER 2.0 + net.Receive("ACF_InvalidateGearboxInfo",function() + local Gearbox = net.ReadEntity() + + if not IsValid(Gearbox) then return end + + Gearbox.HasData = false + end) + + net.Receive("ACF_RequestGearboxInfo",function() + local Gearbox = net.ReadEntity() + local Data = util.JSONToTable(net.ReadString()) + local Inputs = util.JSONToTable(net.ReadString()) + local OutL = util.JSONToTable(net.ReadString()) + local OutR = util.JSONToTable(net.ReadString()) + + local InEnts = {} + local OutLEnts = {} + local OutREnts = {} + + for _,E in ipairs(Inputs) do + local Ent = Entity(E) + + InEnts[#InEnts + 1] = {Ent = Ent} + end + + for _,E in ipairs(OutL) do + local Ent = Entity(E) + local Pos = Vector() + + if Ent:GetClass() == "acf_gearbox" then + Pos = Ent:WorldToLocal(Ent:GetAttachment(Ent:LookupAttachment("input")).Pos) + end + + OutLEnts[#OutLEnts + 1] = {Ent = Ent, Pos = Pos} + end + for _,E in ipairs(OutR) do + local Ent = Entity(E) + local Pos = Vector() + + if Ent:GetClass() == "acf_gearbox" then + Pos = Ent:WorldToLocal(Ent:GetAttachment(Ent:LookupAttachment("input")).Pos) + end + + OutREnts[#OutREnts + 1] = {Ent = Ent, Pos = Pos} + end + + Gearbox.Inputs = InEnts + Gearbox.OutputsL = OutLEnts + Gearbox.OutputsR = OutREnts + + Gearbox.In = Data.In + Gearbox.OutL = Data.OutL + Gearbox.OutR = Data.OutR + + Gearbox.IsStraight = (Data.OutL == Data.OutR) + + Gearbox.HasData = true + Gearbox.Age = Clock.CurTime + 5 + + Queued[Gearbox] = nil + end) + + function ENT:RequestGearboxInfo() + if Queued[self] then return end + + Queued[self] = true + + timer.Simple(5, function() Queued[self] = nil end) + + net.Start("ACF_RequestGearboxInfo") + net.WriteEntity(self) + net.SendToServer() + end +end + +do -- Overlay + -- Rendered is used to prevent re-rendering as part of the extended link rendering + -- Focus will render links different to show what is linked + + local orange = Color(255,127,0) + local teal = Color(0,195,255) + local red = Color(255,0,0) + local green = Color(0,255,0) + function ENT:DrawLinks(Rendered) + if Rendered[self] then return end + local SelfTbl = self:GetTable() + + Rendered[self] = true + + if not SelfTbl.HasData then + self:RequestGearboxInfo() + return + elseif Clock.CurTime > SelfTbl.Age then + self:RequestGearboxInfo() + end + + local Perc = (Clock.CurTime / 2) % 1 + + local InPos = self:LocalToWorld(SelfTbl.In) + local LeftPos = self:LocalToWorld(SelfTbl.OutL) + local RightPos = self:LocalToWorld(SelfTbl.OutR) + + -- Rendering more along the chain + for _,T in ipairs(SelfTbl.Inputs) do + local E = T.Ent + + if IsValid(E) and E.DrawLinks then + E:DrawLinks(Rendered,false) + end + end + + for _,T in ipairs(SelfTbl.OutputsL) do + local E = T.Ent + + if IsValid(E) then + + local Pos = E:LocalToWorld(T.Pos) + render.DrawBeam(LeftPos, Pos, 2, 0, 0, color_black) + render.DrawBeam(LeftPos, Pos, 1.5, 0, 0, color_white) + local SpherePos = LerpVector(Perc, LeftPos, Pos) + render.DrawSphere(SpherePos, 2, 4, 3, orange) + + if E.DrawLinks then + E:DrawLinks(Rendered,false) + else -- prop + render.DrawSphere(Pos, 2, 4, 3, teal) + end + end + end + + for _,T in ipairs(SelfTbl.OutputsR) do + local E = T.Ent + + if IsValid(E) then + + local Pos = E:LocalToWorld(T.Pos) + render.DrawBeam(RightPos, Pos, 2, 0, 0, color_black) + render.DrawBeam(RightPos, Pos, 1.5, 0, 0, color_white) + local SpherePos = LerpVector(Perc, RightPos, Pos) + render.DrawSphere(SpherePos, 2, 4, 3, orange) + + if E.DrawLinks then + E:DrawLinks(Rendered,false) + else -- prop + render.DrawSphere(Pos, 2, 4, 3, teal) + end + end + end + + render.DrawSphere(InPos, 2, 4, 3, green) + render.DrawSphere(LeftPos, 2, 4, 3, red) + if not SelfTbl.IsStraight then + render.DrawSphere(RightPos, 2, 4, 3, red) + end + end + + function ENT:DrawOverlay() + local SelfTbl = self:GetTable() + + if not SelfTbl.HasData then + self:RequestGearboxInfo() + return + elseif Clock.CurTime > SelfTbl.Age then + self:RequestGearboxInfo() + end + + render.SetColorMaterial() + + self:DrawLinks({self = true}, true) + + local InTextPos = self:LocalToWorld(SelfTbl.In):ToScreen() + local OutLTextPos = self:LocalToWorld(SelfTbl.OutL):ToScreen() + local OutRTextPos = self:LocalToWorld(SelfTbl.OutR):ToScreen() + + cam.Start2D() + draw.SimpleTextOutlined("Input","ACF_Title",InTextPos.x,InTextPos.y,color_white,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER,1,color_black) + + if SelfTbl.IsStraight then + draw.SimpleTextOutlined("Output","ACF_Title",OutLTextPos.x,OutLTextPos.y,color_white,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER,1,color_black) + else + draw.SimpleTextOutlined("Left Output","ACF_Title",OutLTextPos.x,OutLTextPos.y,color_white,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER,1,color_black) + draw.SimpleTextOutlined("Right Output","ACF_Title",OutRTextPos.x,OutRTextPos.y,color_white,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER,1,color_black) + end + cam.End2D() + end +end \ No newline at end of file diff --git a/lua/entities/acf_gearbox/init.lua b/lua/entities/acf_gearbox/init.lua index d6425c4a3..6c8a3e16f 100644 --- a/lua/entities/acf_gearbox/init.lua +++ b/lua/entities/acf_gearbox/init.lua @@ -608,6 +608,8 @@ do -- Linking ------------------------------------------ end end) + Gearbox:InvalidateClientInfo() + return true, "Wheel linked successfully!" end @@ -623,6 +625,8 @@ do -- Linking ------------------------------------------ Gearbox.GearboxOut[Target] = Link Target.GearboxIn[Gearbox] = true + Gearbox:InvalidateClientInfo() + return true, "Gearbox linked successfully!" end @@ -644,6 +648,8 @@ do -- Unlinking ---------------------------------------- Wheel:RemoveCallOnRemove("ACF_GearboxUnlink" .. Gearbox:EntIndex()) + Gearbox:InvalidateClientInfo() + return true, "Wheel unlinked successfully!" end @@ -661,6 +667,8 @@ do -- Unlinking ---------------------------------------- Gearbox.GearboxOut[Target] = nil Target.GearboxIn[Gearbox] = nil + Gearbox:InvalidateClientInfo() + return true, "Gearbox unlinked successfully!" end @@ -999,6 +1007,72 @@ do -- Duplicator Support ------------------------------- end end ---------------------------------------------------- +do -- NET SURFER 2.0 + util.AddNetworkString("ACF_RequestGearboxInfo") + util.AddNetworkString("ACF_InvalidateGearboxInfo") + + function ENT:InvalidateClientInfo() + net.Start("ACF_InvalidateGearboxInfo") + net.WriteEntity(self) + net.Broadcast() + end + + net.Receive("ACF_RequestGearboxInfo",function(_,Ply) + local Entity = net.ReadEntity() + + if IsValid(Entity) then + local Inputs = {} + local OutputL = {} + local OutputR = {} + local Data = { + In = Entity.In, + OutL = Entity.OutL, + OutR = Entity.OutR + } + + if next(Entity.GearboxIn) then + for E in pairs(Entity.GearboxIn) do + Inputs[#Inputs + 1] = E:EntIndex() + end + end + + if next(Entity.Engines) then + for E in pairs(Entity.Engines) do + Inputs[#Inputs + 1] = E:EntIndex() + end + end + + if next(Entity.GearboxOut) then + for E,L in pairs(Entity.GearboxOut) do + if L.Side == 0 then + OutputL[#OutputL + 1] = E:EntIndex() + else + OutputR[#OutputR + 1] = E:EntIndex() + end + end + end + + if next(Entity.Wheels) then + for E,L in pairs(Entity.Wheels) do + if L.Side == 0 then + OutputL[#OutputL + 1] = E:EntIndex() + else + OutputR[#OutputR + 1] = E:EntIndex() + end + end + end + + net.Start("ACF_RequestGearboxInfo") + net.WriteEntity(Entity) + net.WriteString(util.TableToJSON(Data)) + net.WriteString(util.TableToJSON(Inputs)) + net.WriteString(util.TableToJSON(OutputL)) + net.WriteString(util.TableToJSON(OutputR)) + net.Send(Ply) + end + end) +end + do -- Miscellaneous ------------------------------------ function ENT:Enable() if self.Automatic then diff --git a/lua/entities/acf_turret/cl_init.lua b/lua/entities/acf_turret/cl_init.lua index bca69e9d2..127dc4c33 100644 --- a/lua/entities/acf_turret/cl_init.lua +++ b/lua/entities/acf_turret/cl_init.lua @@ -11,6 +11,14 @@ language.Add("Cleanup__acf_turret", "Cleaned up all ACF turrets!") language.Add("SBoxLimit__acf_turret", "You've reached the ACF turrets limit!") do -- NET SURFER + net.Receive("ACF_InvalidateTurretInfo",function() + local Turret = net.ReadEntity() + + if not IsValid(Turret) then return end + + Turret.HasData = false + end) + net.Receive("ACF_RequestTurretInfo",function() local Entity = net.ReadEntity() local Rotator = net.ReadEntity() @@ -26,6 +34,32 @@ do -- NET SURFER Entity.CoMDist = Data.CoMDist Entity.Type = Data.Type + local Arc = Data.MaxDeg - Data.MinDeg + local HasArc = Arc ~= 360 + + Entity.HasArc = HasArc + if HasArc then + local Fidelity = math.ceil(Arc / 15) + local ArcPos = {} + + local Wedge = Arc / Fidelity + for I = 0, Fidelity do + local Ang = math.rad(Data.MinDeg + (Wedge * I)) + ArcPos[I] = Vector(math.cos(Ang), -math.sin(Ang), 0) + end + + Entity.ArcPos = ArcPos + Entity.Fidelity = Fidelity + + local MinRad = math.rad(Data.MinDeg) + Entity.MinPos = Vector(math.cos(MinRad - math.rad(1)), -math.sin(MinRad - math.rad(1)), 0) + Entity.MinPos2 = Vector(math.cos(MinRad), -math.sin(MinRad), 0) + + local MaxRad = math.rad(Data.MaxDeg) + Entity.MaxPos = Vector(math.cos(MaxRad), -math.sin(MaxRad), 0) + Entity.MaxPos2 = Vector(math.cos(MaxRad + math.rad(1)), -math.sin(MaxRad + math.rad(1)), 0) + end + Entity.HasData = true Entity.Age = Clock.CurTime + 5 @@ -37,6 +71,8 @@ do -- NET SURFER Queued[self] = true + timer.Simple(5, function() Queued[self] = nil end) + net.Start("ACF_RequestTurretInfo") net.WriteEntity(self) net.SendToServer() @@ -44,64 +80,32 @@ do -- NET SURFER end do -- Turret drive drawing - local DrawDist = 1024 ^ 2 local HideInfo = ACF.HideInfoBubble - local function CSModel(Ent) - if not IsValid(Ent) then return end - - if IsValid(Ent.CSModel) then - if Ent.CSModel:GetModel() ~= Ent:GetModel() then Ent.CSModel:Remove() return end - - return Ent.CSModel - end - - if not Ent.Matrix then return end - - local CSModel = ClientsideModel(Ent:GetModel()) - CSModel:SetParent(Ent) - CSModel:SetPos(Ent:GetPos()) - CSModel:SetAngles(Ent:GetAngles()) - CSModel:SetMaterial(Ent:GetMaterial()) - CSModel:SetColor(Ent:GetColor()) - - CSModel.Material = Ent:GetMaterial() - CSModel.Matrix = Ent.Matrix - CSModel:EnableMatrix("RenderMultiply", CSModel.Matrix) - - Ent.CSModel = CSModel - - return Ent.CSModel - end - function ENT:Draw() - -- Partial from base_wire_entity, need the tooltip but without the model drawing since we're drawing our own local looked_at = self:BeingLookedAtByLocalPlayer() if looked_at then self:DrawEntityOutline() - if not HideInfo() then self:AddWorldTip() end end local Rotator = self:GetNWEntity("ACF.Rotator") - if (not IsValid(Rotator)) or ((EyePos()):DistToSqr(self:GetPos()) > DrawDist) then self:DrawModel() return end - - local CSM = CSModel(self) - if not IsValid(CSM) then self:DrawModel() return end - - if CSM.Material ~= self:GetMaterial() then CSM:Remove() return end - if CSM:GetColor() ~= self:GetColor() then CSM:Remove() return end - if CSM.Matrix ~= self.Matrix then CSM:Remove() return end + if IsValid(Rotator) and self.Matrix then + self.Matrix:SetAngles(self:WorldToLocalAngles(Rotator:GetAngles())) + self:EnableMatrix("RenderMultiply", self.Matrix) + end - if CSM:GetParent() ~= self then CSM:Remove() return end + self:DrawModel() - CSM:SetAngles(Rotator:GetAngles()) - end + if looked_at and not HideInfo() then + self:AddWorldTip() - function ENT:OnRemove() - if IsValid(self.CSModel) then self.CSModel:Remove() end + if LocalPlayer():GetActiveWeapon():GetClass() == "weapon_physgun" and not LocalPlayer():InVehicle() then + self:DrawHome() + end + end end end @@ -110,8 +114,11 @@ do -- Overlay local green = Color(0,255,0) local orange = Color(255,127,0) local magenta = Color(255,0,255) + local arcColor = Color(0,255,255,128) + local curColor = Color(125,255,0) + local Mat = Material("vgui/white") - function ENT:DrawOverlay(Trace) + function ENT:DrawHome() if not self.HasData then self:RequestTurretInfo() @@ -120,79 +127,171 @@ do -- Overlay self:RequestTurretInfo() end - local UX = self:GetUp() * 0.5 - local X = self:OBBMaxs().x - local Pos = self:LocalToWorld(self:OBBCenter()) + render.SetMaterial(Mat) + + local FWD = self:GetForward() + local X = math.max(self:OBBMaxs().x, self:OBBMaxs().z) + local UX = X / 10 + local LocPos = self:WorldToLocal(EyePos()) + + local LocalRightDir = Vector(0,LocPos.y,LocPos.z):GetNormalized() + LocalRightDir:Rotate(Angle(0,0,90)) + local WorldRightDir = self:LocalToWorld(LocalRightDir) - self:GetPos() if self.Type == "Turret-V" then - UX = self:GetRight() * 0.5 + X = self:OBBMaxs().z end - render.DrawLine(Pos + UX,Pos + (self:GetForward() * X) + UX,orange,true) + local Pos = self:LocalToWorld(self:OBBCenter()) + local Origin = Pos + (FWD * X * 1.1) + + --debugoverlay.Text(Origin, tostring(Axis), 0.015, false) - if IsValid(self.Rotator) then render.DrawLine(Pos,Pos + self.Rotator:GetForward() * X,color_white,true) end + render.DrawQuad(Pos, Pos, Pos + (FWD * X * 1.1) + (WorldRightDir * -UX / 4), Pos + (FWD * X * 1.1) + (WorldRightDir * UX / 4), orange) + render.DrawQuad(Origin + FWD * UX, Origin + WorldRightDir * UX + FWD * (-UX / 2), Origin, Origin + WorldRightDir * -UX + FWD * (-UX / 2), orange) + end + + local NoAng = Angle(0,0,0) + function ENT:DrawOverlay(Trace) + local SelfTbl = self:GetTable() + + if not SelfTbl.HasData then + self:RequestTurretInfo() + + return + elseif Clock.CurTime > SelfTbl.Age then + self:RequestTurretInfo() + end + + render.SetMaterial(Mat) + + local Up = self:GetUp() + local FWD = self:GetForward() + + local LocEyePos = self:WorldToLocal(EyePos()) + local LocalRightDir = Vector(0,LocEyePos.y,LocEyePos.z):GetNormalized() + LocalRightDir:Rotate(Angle(0,0,90)) + local WorldRightDir = self:LocalToWorld(LocalRightDir) - self:GetPos() + + local X = math.max(self:OBBMaxs().x, self:OBBMaxs().z) + local UX = X / 10 + local Pos = self:LocalToWorld(self:OBBCenter()) + + local Rotation = Up:Angle() + + local Rotate = false + if SelfTbl.Type == "Turret-V" then + Right = -self:GetUp() + Up = self:GetRight() + + Rotation = self:GetRight():Angle() + Rotate = true + end + + local Sign = (Rotation:Forward():Dot((EyePos() - Pos):GetNormalized()) < 0) and -1 or 1 local LocPos = self:WorldToLocal(Trace.HitPos) - local AimAng = 0 - local CurAng = 0 local LocDir = Vector(LocPos.x,LocPos.y,0):GetNormalized() - local HasArc = not ((self.MinDeg == -180) and (self.MaxDeg == 180)) + local AimAng = -math.Round(self:WorldToLocalAngles(self:LocalToWorldAngles(LocDir:Angle())).yaw,2) + local CurAng = -math.Round(self:WorldToLocalAngles(SelfTbl.Rotator:GetAngles()).yaw,2) - if self.Type == "Turret-V" then + if Rotate then LocDir = Vector(LocPos.x,0,LocPos.z):GetNormalized() AimAng = -math.Round(self:WorldToLocalAngles(self:LocalToWorldAngles(LocDir:Angle())).pitch,2) - CurAng = -math.Round(self:WorldToLocalAngles(self.Rotator:GetAngles()).pitch,2) - else - AimAng = -math.Round(self:WorldToLocalAngles(self:LocalToWorldAngles(LocDir:Angle())).yaw,2) - CurAng = -math.Round(self:WorldToLocalAngles(self.Rotator:GetAngles()).yaw,2) + CurAng = -math.Round(self:WorldToLocalAngles(SelfTbl.Rotator:GetAngles()).pitch,2) end - render.DrawLine(Pos - UX,self:LocalToWorld(self:OBBCenter() + LocDir * X * 2) - UX,magenta,true) - - render.DrawLine(self:LocalToWorld(self:OBBCenter()),self.Rotator:LocalToWorld(self.LocalCoM),red,true) + render.DrawLine(self:LocalToWorld(self:OBBCenter()),SelfTbl.Rotator:LocalToWorld(SelfTbl.LocalCoM),red,true) render.OverrideDepthEnable(true,true) - render.DrawWireframeSphere(self.Rotator:LocalToWorld(self.LocalCoM),1.5,4,3,red) + render.DrawWireframeSphere(SelfTbl.Rotator:LocalToWorld(SelfTbl.LocalCoM),1.5,4,3,red) render.OverrideDepthEnable(false,false) local MinArcPos = {} local MaxArcPos = {} - if HasArc then - local MinDir = Vector(X,0,0) - local MaxDir = Vector(X,0,0) + if SelfTbl.HasArc then + local MinDir = Vector(X * 0.95,0,0) + local MaxDir = Vector(X * 0.95,0,0) - if self.Type == "Turret-V" then - MinDir:Rotate(Angle(-self.MinDeg,0,0)) - MaxDir:Rotate(Angle(-self.MaxDeg,0,0)) + if Rotate then + MinDir:Rotate(Angle(-SelfTbl.MinDeg,0,0)) + MaxDir:Rotate(Angle(-SelfTbl.MaxDeg,0,0)) else - MinDir:Rotate(Angle(0,-self.MinDeg,0)) - MaxDir:Rotate(Angle(0,-self.MaxDeg,0)) + MinDir:Rotate(Angle(0,-SelfTbl.MinDeg,0)) + MaxDir:Rotate(Angle(0,-SelfTbl.MaxDeg,0)) + end + + local ArcPos = SelfTbl.ArcPos + local ArcAngle = self:LocalToWorldAngles(Angle(0,0,Rotate and -90 or 0)) + local NearDist = X * (1 + (0.025 * -Sign)) * 0.95 + local FarDist = X * (1 + (0.025 * Sign)) * 0.95 + + if Rotate then + NearDist = X * (1 + (0.025 * Sign)) * 0.95 + FarDist = X * (1 + (0.025 * -Sign)) * 0.95 + end + + for I = 0, SelfTbl.Fidelity - 1 do + local Arc1 = LocalToWorld(ArcPos[I],NoAng,Pos,ArcAngle) - Pos + local Arc2 = LocalToWorld(ArcPos[I + 1],NoAng,Pos,ArcAngle) - Pos + + render.DrawQuad(Pos + Arc1 * NearDist, Pos + Arc1 * FarDist, Pos + Arc2 * FarDist, Pos + Arc2 * NearDist, arcColor) + end + + local NearLineDist = X * (1 + (0.05 * -Sign)) * 0.95 + local FarLineDist = X * (1 + (0.05 * Sign)) * 0.95 + + if Rotate then + NearLineDist = X * (1 + (0.05 * Sign)) * 0.95 + FarLineDist = X * (1 + (0.05 * -Sign)) * 0.95 end - render.DrawLine(Pos - UX * 2,self:LocalToWorld(self:OBBCenter() + MinDir) - UX * 2,red,true) - render.DrawLine(Pos - UX * 2,self:LocalToWorld(self:OBBCenter() + MaxDir) - UX * 2,green,true) + local MinArc1 = LocalToWorld(SelfTbl.MinPos,NoAng,Pos,ArcAngle) - Pos + local MinArc2 = LocalToWorld(SelfTbl.MinPos2,NoAng,Pos,ArcAngle) - Pos + render.DrawQuad(Pos + MinArc1 * NearLineDist, Pos + MinArc1 * FarLineDist, Pos + MinArc2 * FarLineDist, Pos + MinArc2 * NearLineDist, red) + + local MaxArc1 = LocalToWorld(SelfTbl.MaxPos,NoAng,Pos,ArcAngle) - Pos + local MaxArc2 = LocalToWorld(SelfTbl.MaxPos2,NoAng,Pos,ArcAngle) - Pos + render.DrawQuad(Pos + MaxArc1 * NearLineDist, Pos + MaxArc1 * FarLineDist, Pos + MaxArc2 * FarLineDist, Pos + MaxArc2 * NearLineDist, green) - MinArcPos = (self:LocalToWorld(self:OBBCenter() + MinDir) - UX * 2):ToScreen() - MaxArcPos = (self:LocalToWorld(self:OBBCenter() + MaxDir) - UX * 2):ToScreen() + MinArcPos = (self:LocalToWorld(self:OBBCenter() + MinDir)):ToScreen() + MaxArcPos = (self:LocalToWorld(self:OBBCenter() + MaxDir)):ToScreen() end - local HomePos = (Pos + UX + self:GetForward() * X):ToScreen() - local CurPos = (Pos + self.Rotator:GetForward() * X):ToScreen() - local AimPos = (self:LocalToWorld(self:OBBCenter() + LocDir * X) - UX):ToScreen() + local Origin = Pos + (self:GetForward() * X * 1.1) + render.DrawQuad(Origin + FWD * UX, Origin + WorldRightDir * UX + FWD * (-UX / 2), Origin, Origin + WorldRightDir * -UX + FWD * (-UX / 2), orange) + + if IsValid(SelfTbl.Rotator) then + local Rotator = SelfTbl.Rotator + local RotFWD = Rotator:GetForward() + local RotRGT = Rotator:GetRight() * Sign + if Rotate then + RotRGT = Rotator:GetUp() * -Sign + end + local RotOrigin = Pos + Rotator:GetForward() * X + + render.DrawQuad(RotOrigin + RotRGT * UX * 0.25 + RotFWD * UX * -1.5, RotOrigin + -RotRGT * UX * 0.25 + RotFWD * UX * -1.5, RotOrigin, RotOrigin, curColor) + end + + render.DrawLine(Pos,self:LocalToWorld(self:OBBCenter() + LocDir * X * 2),magenta,true) + + local HomePos = (Pos + self:GetForward() * X * 1.125):ToScreen() + local CurPos = (Pos + SelfTbl.Rotator:GetForward() * X * 0.925):ToScreen() + local AimPos = (self:LocalToWorld(self:OBBCenter() + LocDir * X)):ToScreen() local CoMPos = (self.Rotator:LocalToWorld(self.LocalCoM) - Vector(0,0,2)):ToScreen() cam.Start2D() - draw.SimpleTextOutlined("Zero","DermaDefault",HomePos.x,HomePos.y,orange,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER,1,color_black) - draw.SimpleTextOutlined("Current: " .. CurAng,"DermaDefault",CurPos.x,CurPos.y,color_white,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER,1,color_black) - draw.SimpleTextOutlined("Aim: " .. AimAng,"DermaDefault",AimPos.x,AimPos.y,magenta,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER,1,color_black) + draw.SimpleTextOutlined("Home","ACF_Title",HomePos.x,HomePos.y,orange,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER,1,color_black) + draw.SimpleTextOutlined("Current: " .. CurAng,"ACF_Title",CurPos.x,CurPos.y,curColor,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER,1,color_black) + draw.SimpleTextOutlined("Aim: " .. AimAng,"ACF_Title",AimPos.x,AimPos.y,magenta,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER,1,color_black) - draw.SimpleTextOutlined("Mass: " .. self.Mass .. "kg","DermaDefault",CoMPos.x,CoMPos.y,color_white,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER,1,color_black) - draw.SimpleTextOutlined("Lateral Distance: " .. self.CoMDist .. "u","DermaDefault",CoMPos.x,CoMPos.y + 16,color_white,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER,1,color_black) + draw.SimpleTextOutlined("Mass: " .. SelfTbl.Mass .. "kg","ACF_Control",CoMPos.x,CoMPos.y,color_white,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER,1,color_black) + draw.SimpleTextOutlined("Lateral Distance: " .. SelfTbl.CoMDist .. "u","ACF_Control",CoMPos.x,CoMPos.y + 16,color_white,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER,1,color_black) - if HasArc then - draw.SimpleTextOutlined("Min: " .. self.MinDeg,"DermaDefault",MinArcPos.x,MinArcPos.y,red,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER,1,color_black) - draw.SimpleTextOutlined("Max: " .. self.MaxDeg,"DermaDefault",MaxArcPos.x,MaxArcPos.y,green,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER,1,color_black) + if SelfTbl.HasArc then + draw.SimpleTextOutlined("Min: " .. SelfTbl.MinDeg,"ACF_Control",MinArcPos.x,MinArcPos.y,red,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER,1,color_black) + draw.SimpleTextOutlined("Max: " .. SelfTbl.MaxDeg,"ACF_Control",MaxArcPos.x,MaxArcPos.y,green,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER,1,color_black) end cam.End2D() end diff --git a/lua/entities/acf_turret/init.lua b/lua/entities/acf_turret/init.lua index 6ff8543e7..506ac1afe 100644 --- a/lua/entities/acf_turret/init.lua +++ b/lua/entities/acf_turret/init.lua @@ -94,6 +94,10 @@ do -- Spawn and Update funcs Entity.Turret = Data.Turret Entity.ID = Turret.ID + local SizePerc = (Size - Turret.Size.Min) / (Turret.Size.Max - Turret.Size.Min) + local MaxMass = ((Turret.MassLimit.Min * (1 - SizePerc)) + (Turret.MassLimit.Max * SizePerc)) ^ 2 + Entity.MaxMass = MaxMass + Entity.TurretData = { Teeth = Class.GetTeethCount(Turret,Size), RingSize = Size, @@ -101,7 +105,8 @@ do -- Spawn and Update funcs TotalMass = 0, LocalCoM = Vector(), Tilt = 1, - TurretClass = Data.Turret + TurretClass = Data.Turret, + MaxMass = MaxMass } -- Type-specific functions that differ between horizontal and vertical turret components @@ -159,6 +164,11 @@ do -- Spawn and Update funcs Entity.MotorMaxSpeed = 1 Entity.MotorGearRatio = 1 Entity.EffortScale = 1 + Entity.Complexity = 1 + + local MaxSpeed = Data.MaxSpeed or 0 + Entity.SpeedLimited = MaxSpeed > 0 + Entity.MaxSpeed = MaxSpeed if Entity.SoundPlaying == true then Sounds.SendAdjustableSound(Entity,true) @@ -166,7 +176,6 @@ do -- Spawn and Update funcs Entity.SoundPlaying = false Entity.SoundPath = Entity.HandGear.Sound - local SizePerc = (Size - Turret.Size.Min) / (Turret.Size.Max - Turret.Size.Min) Entity.ScaledArmor = (Turret.Armor.Min * (1 - SizePerc)) + (Turret.Armor.Max * SizePerc) WireIO.SetupInputs(Entity, Inputs, Data, Class, Turret) @@ -194,6 +203,13 @@ do -- Spawn and Update funcs ------------------ util.AddNetworkString("ACF_RequestTurretInfo") + util.AddNetworkString("ACF_InvalidateTurretInfo") + + function ENT:InvalidateClientInfo() + net.Start("ACF_InvalidateTurretInfo") + net.WriteEntity(self) + net.Broadcast() + end net.Receive("ACF_RequestTurretInfo",function(_, Player) local Entity = net.ReadEntity() @@ -294,7 +310,7 @@ do -- Spawn and Update funcs return Entity end - Entities.Register("acf_turret", MakeACF_Turret, "Turret", "RingSize", "MinDeg", "MaxDeg") + Entities.Register("acf_turret", MakeACF_Turret, "Turret", "RingSize", "MinDeg", "MaxDeg", "MaxSpeed") function ENT:Update(Data) VerifyData(Data) @@ -371,13 +387,15 @@ do -- Spawn and Update funcs end local function Proxy_ACF_OnParent(self, _, _) - if (not IsValid(self.ACF_TurretAncestor)) or (not Contraption.HasAncestor(self, self.ACF_TurretAncestor)) then self.CFW_OnParented = nil self.ACF_TurretAncestor = nil return end + local SelfTbl = self:GetTable() + if (not IsValid(SelfTbl.ACF_TurretAncestor)) or (not Contraption.HasAncestor(self, SelfTbl.ACF_TurretAncestor)) then self.CFW_OnParented = nil self.ACF_TurretAncestor = nil return end self.ACF_TurretAncestor:UpdateTurretMass(false) end local function Proxy_ACF_OnMassChange(self) - if (not IsValid(self.ACF_TurretAncestor)) or (not Contraption.HasAncestor(self, self.ACF_TurretAncestor)) then self.ACF_OnMassChange = nil self.ACF_TurretAncestor = nil return end + local SelfTbl = self:GetTable() + if (not IsValid(SelfTbl.ACF_TurretAncestor)) or (not Contraption.HasAncestor(self, SelfTbl.ACF_TurretAncestor)) then self.ACF_OnMassChange = nil self.ACF_TurretAncestor = nil return end self.ACF_TurretAncestor:UpdateTurretMass(false) end @@ -392,6 +410,10 @@ do -- Spawn and Update funcs Entity.ACF_OnMassChange = nil Entity.ACF_TurretAncestor = nil end + + if IsValid(Turret) then + Turret:InvalidateClientInfo() + end end local function BuildWatchlist(Entity) -- Potentially hot and heavy, should only be triggered after a (maximum) delay to catch large changes and not every single new entity @@ -480,6 +502,8 @@ do -- Spawn and Update funcs local function GetSubTurretMass(Entity) -- Returns mass center (local to rotator) and amount from all subturrets if not IsValid(Entity) then return end + Entity.Complexity = 1 + if next(Entity.SubTurrets) == nil then return Vector(), 0 end local Mass = 0 @@ -489,6 +513,10 @@ do -- Spawn and Update funcs for k in pairs(Entity.SubTurrets) do if not IsValid(k) then continue end + if (k.Turret == Entity.Turret) and (k.TurretData.RingSize > (Entity.TurretData.RingSize * 0.5)) then + Entity.Complexity = Entity.Complexity * math.Clamp(((Entity.TurretData.RingSize * 0.5) / k.TurretData.RingSize) ^ 2, 0, 1) + end + Mass = Mass + k:GetTotalMass() + k.ACF.Mass AddCoM[k] = true end @@ -506,40 +534,48 @@ do -- Spawn and Update funcs return CoM, Mass end + function ENT:UpdateSound() + local SoundPath = self.HandGear.Sound + + if IsValid(self.Motor) then + SoundPath = self.Motor.SoundPath + end + + self.SoundPath = SoundPath + end + function ENT:UpdateTurretSlew() - local SlewInput = self.HandGear + local SelfTbl = self:GetTable() + local SlewInput = SelfTbl.HandGear local Stabilized = false local StabilizeAmount = 0 - local SoundPath = SlewInput.Sound - local MaxDistance = (((self.TurretData.RingSize / 2) * 1.2) + 12) ^ 2 + local MaxDistance = (((SelfTbl.TurretData.RingSize / 2) * 1.2) + 12) ^ 2 - if IsValid(self.Motor) and self.Motor:GetPos():DistToSqr(self:GetPos()) > MaxDistance then + if IsValid(SelfTbl.Motor) and SelfTbl.Motor:GetPos():DistToSqr(self:GetPos()) > MaxDistance then local USound = UnlinkSound:format(math.random(1, 3)) Sounds.SendSound(self, USound, 70, 100, 1) - Sounds.SendSound(self.Motor, USound, 70, 100, 1) - self:Unlink(self.Motor) + Sounds.SendSound(SelfTbl.Motor, USound, 70, 100, 1) + self:Unlink(SelfTbl.Motor) -- No sense checking for this separately since it can't function without the motor anyway -- Using separate link distance as gyros can be parented to other things - if IsValid(self.Gyro) and self.Gyro:GetPos():DistToSqr(self:GetPos()) > MaxLinkDistance then + if IsValid(SelfTbl.Gyro) and SelfTbl.Gyro:GetPos():DistToSqr(self:GetPos()) > MaxLinkDistance then Sounds.SendSound(self, USound, 70, 100, 1) - Sounds.SendSound(self.Gyro, USound, 70, 100, 1) - self:Unlink(self.Gyro) + Sounds.SendSound(SelfTbl.Gyro, USound, 70, 100, 1) + self:Unlink(SelfTbl.Gyro) end end - if IsValid(self.Motor) and self.Motor:IsActive() then - SlewInput = self.Motor:GetInfo() - Stabilized = IsValid(self.Gyro) and self.Gyro:IsActive() - if Stabilized then StabilizeAmount = self.Gyro:GetInfo() end - - SoundPath = self.Motor.SoundPath + if IsValid(self.Motor) and SelfTbl.Motor:IsActive() then + SlewInput = SelfTbl.Motor:GetInfo() + Stabilized = IsValid(SelfTbl.Gyro) and SelfTbl.Gyro:IsActive() + if Stabilized then StabilizeAmount = SelfTbl.Gyro:GetInfo() end end -- Scale for being off-axis, further affects friction local Tilt = 1 - if self.Turret == "Turret-V" then + if SelfTbl.Turret == "Turret-V" then Tilt = math.max(1 - self:GetRight():Dot(vector_up), 0) else Tilt = math.max(self:GetUp():Dot(vector_up), 0) @@ -547,23 +583,25 @@ do -- Spawn and Update funcs self.TurretData.Tilt = Tilt - local SlewData = self.ClassData.CalcSpeed(self.TurretData,SlewInput) + local SlewData = self.ClassData.CalcSpeed(SelfTbl.TurretData,SlewInput) -- Allowing vertical turret drives to have a small amount of stabilization, but only if they aren't powered and the mass is well balanced -- Think about certain turrets in WW2 where the gun was vertically aimed by the gunner with his shoulder -- Only going to allow at most 25% so it's always better to motorize the drive and link a gyro to it -- Also limited to 125mm distance from center of drive, where it will be strongest at the center - if (self.ID == "Turret-V") and ((self.TurretData.LocalCoM:Length2DSqr() * ACF.InchToMm) < (125 ^ 2)) and not IsValid(self.Motor) then + if (SelfTbl.ID == "Turret-V") and ((SelfTbl.TurretData.LocalCoM:Length2DSqr() * ACF.InchToMm) < (125 ^ 2)) and not IsValid(SelfTbl.Motor) then Stabilized = true - StabilizeAmount = (1 - ((self.TurretData.LocalCoM:Length2DSqr() * ACF.InchToMm) / (125 ^ 2))) * 0.25 + StabilizeAmount = (1 - ((SelfTbl.TurretData.LocalCoM:Length2DSqr() * ACF.InchToMm) / (125 ^ 2))) * 0.25 end self.MotorMaxSpeed = SlewData.MotorMaxSpeed or 1 -- Both this and MotorGearRatio are used for sound calculations self.MotorGearRatio = SlewData.MotorGearRatio or 1 - self.SoundPath = SoundPath - self.MaxSlewRate = SlewData.MaxSlewRate - self.SlewAccel = SlewData.SlewAccel + self:UpdateSound() + + self.MaxSlewRate = SlewData.MaxSlewRate * SelfTbl.Complexity + if SelfTbl.SpeedLimited then self.MaxSlewRate = math.min(self.MaxSlewRate, SelfTbl.MaxSpeed) end + self.SlewAccel = SlewData.SlewAccel * SelfTbl.Complexity self.EffortScale = SlewData.EffortScale or 1 -- Sound scaling self.Stabilized = Stabilized self.StabilizeAmount = StabilizeAmount @@ -594,12 +632,17 @@ do -- Spawn and Update funcs end function ENT:CheckCoM(Force) - if (Force == false) and (Clock.CurTime < self.CoMCheckDelay) then return end + local SelfTbl = self:GetTable() + if (Force == false) and (Clock.CurTime < SelfTbl.CoMCheckDelay) then return end self.CoMCheckDelay = Clock.CurTime + 2 + math.Rand(1,2) GetDynamicMass(self) GetSubTurretMass(self) + if SelfTbl.ACF_TurretAncestor then + self.Complexity = (SelfTbl.Complexity or 1) * SelfTbl.ACF_TurretAncestor.Complexity + end + self:GetTotalMass() self:GetTurretMassCenter() @@ -629,22 +672,24 @@ end do -- Overlay function ENT:UpdateOverlayText() - local SlewMax = math.Round(self.MaxSlewRate * self.DamageScale, 2) - local SlewAccel = math.Round(self.SlewAccel * self.DamageScale, 4) - local TotalMass = math.Round(self.TurretData.TotalMass, 1) + local SelfTbl = self:GetTable() + local SlewMax = math.Round(SelfTbl.MaxSlewRate * SelfTbl.DamageScale, 2) + local SlewAccel = math.Round(SelfTbl.SlewAccel * SelfTbl.DamageScale, 4) + local TotalMass = math.Round(SelfTbl.TurretData.TotalMass, 1) + local MaxMass = math.Round(SelfTbl.MaxMass,1) - local Text = "Max " .. SlewMax .. " deg/s\nAccel: " .. SlewAccel .. " deg/s^2\nTeeth: " .. self.TurretData.Teeth .. " t\nCurrent Mass: " .. TotalMass .. " kg" + local Text = "Max " .. SlewMax .. " deg/s\nAccel: " .. SlewAccel .. " deg/s^2\nTeeth: " .. SelfTbl.TurretData.Teeth .. " t\nCurrent Mass: " .. TotalMass .. " kg / " .. MaxMass .. " kg max" - if self.HasArc then Text = Text .. "\nArc: " .. self.MinDeg .. "/" .. self.MaxDeg end + if SelfTbl.HasArc then Text = Text .. "\nArc: " .. SelfTbl.MinDeg .. "/" .. SelfTbl.MaxDeg end - if IsValid(self.Motor) then Text = Text .. "\nMotor: " .. tostring(self.Motor) end + if IsValid(SelfTbl.Motor) then Text = Text .. "\nMotor: " .. tostring(SelfTbl.Motor) end - if IsValid(self.Gyro) then Text = Text .. "\nGyro: " .. tostring(self.Gyro) end + if IsValid(SelfTbl.Gyro) then Text = Text .. "\nGyro: " .. tostring(SelfTbl.Gyro) end - if self.Stabilized and IsValid(self.Gyro) and IsValid(self.Motor) then - Text = Text .. "\n\nMotor stabilized at " .. math.Round(self.StabilizeAmount * 100,1) .. "%" - elseif self.Stabilized then - Text = Text .. "\n\nNaturally stabilized at " .. math.Round(self.StabilizeAmount * 100,1) .. "%" + if SelfTbl.Stabilized and IsValid(SelfTbl.Gyro) and IsValid(SelfTbl.Motor) then + Text = Text .. "\n\nMotor stabilized at " .. math.Round(SelfTbl.StabilizeAmount * 100,1) .. "%" + elseif SelfTbl.Stabilized then + Text = Text .. "\n\nNaturally stabilized at " .. math.Round(SelfTbl.StabilizeAmount * 100,1) .. "%" end return Text diff --git a/lua/entities/acf_turret_motor/init.lua b/lua/entities/acf_turret_motor/init.lua index e30a6c8bc..06598288a 100644 --- a/lua/entities/acf_turret_motor/init.lua +++ b/lua/entities/acf_turret_motor/init.lua @@ -69,6 +69,7 @@ do -- Spawn and Update funcs Entity.Motor = Data.Motor Entity.Active = true Entity.SoundPath = Motor.Sound + Entity.DefaultSound = Motor.Sound Entity.Torque = Class.GetTorque(Motor,Size) Entity.Teeth = Data.Teeth diff --git a/lua/weapons/gmod_tool/stools/acfsound.lua b/lua/weapons/gmod_tool/stools/acfsound.lua index 6d73d5d8d..b84b2cc76 100644 --- a/lua/weapons/gmod_tool/stools/acfsound.lua +++ b/lua/weapons/gmod_tool/stools/acfsound.lua @@ -104,6 +104,22 @@ Sounds.acf_piledriver = { end } +Sounds.acf_turret_motor = { + GetSound = function(ent) + return { Sound = ent.SoundPath or ent.DefaultSound } + end, + SetSound = function(ent, soundData) + ent.SoundPath = soundData.Sound + + if IsValid(ent.Turret) then ent.Turret:UpdateSound() end + end, + ResetSound = function(ent) + ent.SoundPath = ent.DefaultSound + + if IsValid(ent.Turret) then ent.Turret:UpdateSound() end + end +} + local function ReplaceSound(_, Entity, Data) if not IsValid(Entity) then return end