From 44829d84ba53169ec5300d847648f301c331e58e Mon Sep 17 00:00:00 2001 From: thecraftianman <64441307+thecraftianman@users.noreply.github.com> Date: Thu, 1 Feb 2024 21:58:49 -0500 Subject: [PATCH] Massive drivetrain optimizations This should hugely improve server performance when it comes to these entities, which seem to be the biggest passive drain on the server. The biggest improvements occur when the throttle is not active, but everything should still be far faster overall as well. --- lua/entities/acf_engine/init.lua | 308 +++++++++++++++++------------ lua/entities/acf_fueltank/init.lua | 92 +++++---- lua/entities/acf_gearbox/init.lua | 78 +++++--- 3 files changed, 293 insertions(+), 185 deletions(-) diff --git a/lua/entities/acf_engine/init.lua b/lua/entities/acf_engine/init.lua index 288b8164c..4068368e2 100644 --- a/lua/entities/acf_engine/init.lua +++ b/lua/entities/acf_engine/init.lua @@ -103,23 +103,24 @@ end -- Local Funcs and Vars --===============================================================================================-- -local Damage = ACF.Damage -local Utilities = ACF.Utilities -local Clock = Utilities.Clock -local Sounds = Utilities.Sounds -local Contraption = ACF.Contraption -local MaxDistance = ACF.LinkDistance * ACF.LinkDistance -local UnlinkSound = "physics/metal/metal_box_impact_bullet%s.wav" -local IsValid = IsValid -local Clamp = math.Clamp -local Round = math.Round -local Remap = math.Remap -local max = math.max -local min = math.min -local TimerCreate = timer.Create -local TimerSimple = timer.Simple -local TimerRemove = timer.Remove -local HookRun = hook.Run +local Damage = ACF.Damage +local Utilities = ACF.Utilities +local Clock = Utilities.Clock +local Sounds = Utilities.Sounds +local Contraption = ACF.Contraption +local MaxDistance = ACF.LinkDistance * ACF.LinkDistance +local UnlinkSound = "physics/metal/metal_box_impact_bullet%s.wav" +local IsValid = IsValid +local Clamp = math.Clamp +local Round = math.Round +local Remap = math.Remap +local max = math.max +local min = math.min +local TimerCreate = timer.Create +local TimerSimple = timer.Simple +local TimerRemove = timer.Remove +local HookRun = hook.Run +local TickInterval = engine.TickInterval local function GetPitchVolume(Engine) local RPM = Engine.FlyRPM @@ -131,15 +132,16 @@ local function GetPitchVolume(Engine) end local function GetNextFuelTank(Engine) - if not next(Engine.FuelTanks) then return end + local FuelTanks = Engine.FuelTanks + if not next(FuelTanks) then return end - local Select = next(Engine.FuelTanks, Engine.FuelTank) or next(Engine.FuelTanks) + local Select = next(FuelTanks, Engine.FuelTank) or next(FuelTanks) local Start = Select repeat if Select:CanConsume() then return Select end - Select = next(Engine.FuelTanks, Select) or next(Engine.FuelTanks) + Select = next(FuelTanks, Select) or next(FuelTanks) until Select == Start return Select:CanConsume() and Select or nil @@ -180,24 +182,25 @@ local function CheckGearboxes(Engine) end end -local function SetActive(Entity, Value) - if Entity.Active == tobool(Value) then return end +local function SetActive(Entity, Value, EntTbl) + EntTbl = EntTbl or Entity:GetTable() + if EntTbl.Active == tobool(Value) then return end - if not Entity.Active then -- Was off, turn on - Entity.Active = true + if not EntTbl.Active then -- Was off, turn on + EntTbl.Active = true - Entity:CalcMassRatio() + Entity:CalcMassRatio(EntTbl) - Entity.LastThink = Clock.CurTime - Entity.Torque = Entity.PeakTorque - Entity.FlyRPM = Entity.IdleRPM * 1.5 + EntTbl.LastThink = Clock.CurTime + EntTbl.Torque = EntTbl.PeakTorque + EntTbl.FlyRPM = EntTbl.IdleRPM * 1.5 - Entity:UpdateSound() + Entity:UpdateSound(EntTbl) - TimerSimple(engine.TickInterval(), function() + TimerSimple(TickInterval(), function() if not IsValid(Entity) then return end - Entity:CalcRPM() + Entity:CalcRPM(EntTbl) end) TimerCreate("ACF Engine Clock " .. Entity:EntIndex(), 3, 0, function() @@ -206,12 +209,12 @@ local function SetActive(Entity, Value) CheckGearboxes(Entity) CheckDistantFuelTanks(Entity) - Entity:CalcMassRatio() + Entity:CalcMassRatio(EntTbl) end) else - Entity.Active = false - Entity.FlyRPM = 0 - Entity.Torque = 0 + EntTbl.Active = false + EntTbl.FlyRPM = 0 + EntTbl.Torque = 0 Entity:DestroySound() @@ -219,7 +222,7 @@ local function SetActive(Entity, Value) end Entity:UpdateOverlay() - Entity:UpdateOutputs() + Entity:UpdateOutputs(EntTbl) end --===============================================================================================-- @@ -366,18 +369,24 @@ do -- Spawn and Update functions Player:AddCleanup("acf_engine", Entity) Player:AddCount(Limit, Entity) - Entity.Owner = Player -- MUST be stored on ent for PP - Entity.Active = false - Entity.Gearboxes = {} - Entity.FuelTanks = {} - Entity.LastThink = 0 - Entity.MassRatio = 1 - Entity.FuelUsage = 0 - Entity.Throttle = 0 - Entity.FlyRPM = 0 - Entity.SoundPath = Engine.Sound - Entity.LastPitch = 0 - Entity.DataStore = Entities.GetArguments("acf_engine") + Entity.Owner = Player -- MUST be stored on ent for PP + Entity.Active = false + Entity.Gearboxes = {} + Entity.FuelTanks = {} + Entity.LastThink = 0 + Entity.MassRatio = 1 + Entity.FuelUsage = 0 + Entity.Throttle = 0 + Entity.FlyRPM = 0 + Entity.SoundPath = Engine.Sound + Entity.LastPitch = 0 + Entity.LastTorque = 0 + Entity.LastFuelUsage = 0 + Entity.LastPower = 0 + Entity.LastRPM = 0 + Entity.LastTotalMass = 0 + Entity.LastPhysMass = 0 + Entity.DataStore = Entities.GetArguments("acf_engine") Entity.revLimiterEnabled = true UpdateEngine(Entity, Data, Class, Engine, Type) @@ -512,26 +521,45 @@ function ENT:Enable() Active = true end - SetActive(self, Active) + SetActive(self, Active, self:GetTable()) self:UpdateOverlay() end function ENT:Disable() - SetActive(self, false) -- Turn off the engine + SetActive(self, false, self:GetTable()) -- Turn off the engine self:UpdateOverlay() end -function ENT:UpdateOutputs() +function ENT:UpdateOutputs(SelfTbl) if not IsValid(self) then return end - local Power = self.Torque * self.FlyRPM / 9548.8 + SelfTbl = SelfTbl or self:GetTable() + local FuelUsage = Round(SelfTbl.FuelUsage) + local Torque = SelfTbl.Torque + local FlyRPM = SelfTbl.FlyRPM + local Power = Round(Torque * FlyRPM / 9548.8) - WireLib.TriggerOutput(self, "Fuel Use", self.FuelUsage) - WireLib.TriggerOutput(self, "Torque", Round(self.Torque)) - WireLib.TriggerOutput(self, "Power", Round(Power)) - WireLib.TriggerOutput(self, "RPM", Round(self.FlyRPM)) + Torque = Round(Torque) + FlyRPM = Round(FlyRPM) + + if SelfTbl.LastFuelUsage ~= FuelUsage then + SelfTbl.LastFuelUsage = FuelUsage + WireLib.TriggerOutput(SelfTbl, "Fuel Use", FuelUsage) + end + if SelfTbl.LastTorque ~= Torque then + SelfTbl.LastTorque = Torque + WireLib.TriggerOutput(SelfTbl, "Torque", Torque) + end + if SelfTbl.LastPower ~= Power then + SelfTbl.LastPower = Power + WireLib.TriggerOutput(SelfTbl, "Power", Power) + end + if SelfTbl.LastRPM ~= FlyRPM then + SelfTbl.LastRPM = FlyRPM + WireLib.TriggerOutput(SelfTbl, "RPM", FlyRPM) + end end local Text = "%s\n\n%s\nPower: %s kW / %s hp\nTorque: %s Nm / %s ft-lb\nPowerband: %s - %s RPM\nRedline: %s RPM" @@ -552,7 +580,7 @@ ACF.AddInputAction("acf_engine", "Throttle", function(Entity, Value) end) ACF.AddInputAction("acf_engine", "Active", function(Entity, Value) - SetActive(Entity, tobool(Value)) + SetActive(Entity, tobool(Value), Entity:GetTable()) end) function ENT:ACF_Activate(Recalc) @@ -587,29 +615,30 @@ function ENT:ACF_OnDamage(DmgResult, DmgInfo) return HitRes end -function ENT:UpdateSound() - local Path = self.SoundPath +function ENT:UpdateSound(SelfTbl) + SelfTbl = SelfTbl or self:GetTable() + local Path = SelfTbl.SoundPath - if Path ~= self.LastSound then + if Path ~= SelfTbl.LastSound then self:DestroySound() - self.LastSound = Path + SelfTbl.LastSound = Path end if Path == "" then return end - if not self.Active then return end + if not SelfTbl.Active then return end - local Pitch, Volume = GetPitchVolume(self) + local Pitch, Volume = GetPitchVolume(SelfTbl) - if math.abs(Pitch - self.LastPitch) < 1 then return end -- Don't bother updating if the pitch difference is too small to notice + if math.abs(Pitch - SelfTbl.LastPitch) < 1 then return end -- Don't bother updating if the pitch difference is too small to notice - self.LastPitch = Pitch + SelfTbl.LastPitch = Pitch - if self.Sound then + if SelfTbl.Sound then Sounds.SendAdjustableSound(self, false, Pitch, Volume) else Sounds.CreateAdjustableSound(self, Path, Pitch, Volume) - self.Sound = true + SelfTbl.Sound = true end end @@ -622,7 +651,8 @@ function ENT:DestroySound() end -- specialized calcmassratio for engines -function ENT:CalcMassRatio() +function ENT:CalcMassRatio(SelfTbl) + SelfTbl = SelfTbl or self:GetTable() local PhysMass = 0 local TotalMass = 0 local Physical, Parented = Contraption.GetEnts(self) @@ -648,101 +678,137 @@ function ENT:CalcMassRatio() end end - self.MassRatio = PhysMass / TotalMass + SelfTbl.MassRatio = PhysMass / TotalMass + TotalMass = Round(TotalMass, 2) + PhysMass = Round(PhysMass, 2) - WireLib.TriggerOutput(self, "Mass", Round(TotalMass, 2)) - WireLib.TriggerOutput(self, "Physical Mass", Round(PhysMass, 2)) + if SelfTbl.LastTotalMass ~= TotalMass then + SelfTbl.LastTotalMass = TotalMass + WireLib.TriggerOutput(SelfTbl, "Mass", Round(TotalMass, 2)) + end + if SelfTbl.LastPhysMass ~= PhysMass then + SelfTbl.LastPhysMass = PhysMass + WireLib.TriggerOutput(SelfTbl, "Physical Mass", Round(PhysMass, 2)) + end end -function ENT:GetConsumption(Throttle, RPM) - if not IsValid(self.FuelTank) then return 0 end +function ENT:GetConsumption(Throttle, RPM, FuelTank, SelfTbl) + SelfTbl = SelfTbl or self:GetTable() + FuelTank = FuelTank or SelfTbl.FuelTank + if not IsValid(FuelTank) then return 0 end - if self.FuelType == "Electric" then - return Throttle * self.FuelUse * self.Torque * RPM * 1.05e-4 + if SelfTbl.FuelType == "Electric" then + return Throttle * SelfTbl.FuelUse * SelfTbl.Torque * RPM * 1.05e-4 else - local IdleConsumption = self.PeakPower * 5e2 - return self.FuelUse * (IdleConsumption + Throttle * self.Torque * RPM) / self.FuelTank.FuelDensity + local IdleConsumption = SelfTbl.PeakPower * 5e2 + return SelfTbl.FuelUse * (IdleConsumption + Throttle * SelfTbl.Torque * RPM) / FuelTank.FuelDensity end end -function ENT:CalcRPM() - if not self.Active then return end +function ENT:CalcRPM(SelfTbl) + -- Reusing these entity table pointers helps us cut down on __index calls + -- This helps to massively improve performance throughout the entire drivetrain + SelfTbl = SelfTbl or self:GetTable() + if not SelfTbl.Active then return end - local DeltaTime = Clock.CurTime - self.LastThink - local FuelTank = GetNextFuelTank(self) + local ClockTime = Clock.CurTime + local DeltaTime = ClockTime - SelfTbl.LastThink + local FuelTank = GetNextFuelTank(SelfTbl) + local IsElectric = SelfTbl.IsElectric + local LimitRPM = SelfTbl.LimitRPM + local FlyRPM = SelfTbl.FlyRPM -- Determine if the rev limiter will engage or disengage - if self.revLimiterEnabled and not self.IsElectric then - if self.FlyRPM > self.LimitRPM * 0.99 then - self.RevLimited = true - elseif self.FlyRPM < self.LimitRPM * 0.95 then - self.RevLimited = false + local RevLimited = false + if SelfTbl.revLimiterEnabled and not IsElectric then + if FlyRPM > LimitRPM * 0.99 then + RevLimited = true + elseif FlyRPM < LimitRPM * 0.95 then + RevLimited = false end + + SelfTbl.RevLimited = RevLimited end - local Throttle = self.RevLimited and 0 or self.Throttle + local Throttle = RevLimited and 0 or SelfTbl.Throttle -- Calculate fuel usage if IsValid(FuelTank) then - self.FuelTank = FuelTank - self.FuelType = FuelTank.FuelType + SelfTbl.FuelTank = FuelTank + SelfTbl.FuelType = FuelTank.FuelType - local Consumption = self:GetConsumption(Throttle, self.FlyRPM) * DeltaTime + local Consumption = self:GetConsumption(Throttle, FlyRPM, FuelTank, SelfTbl) * DeltaTime - self.FuelUsage = 60 * Consumption / DeltaTime + SelfTbl.FuelUsage = 60 * Consumption / DeltaTime - FuelTank:Consume(Consumption) + FuelTank:Consume(Consumption, FuelTank:GetTable()) elseif ACF.RequireFuel then -- Stay active if fuel consumption is disabled - SetActive(self, false) + SetActive(self, false, SelfTbl) - self.FuelUsage = 0 + SelfTbl.FuelUsage = 0 return 0 end -- Calculate the current torque from flywheel RPM - local Percent = Remap(self.FlyRPM, self.IdleRPM, self.LimitRPM, 0, 1) - local PeakRPM = self.IsElectric and self.FlywheelOverride or self.PeakMaxRPM - local Drag = self.PeakTorque * (max(self.FlyRPM - self.IdleRPM, 0) / PeakRPM) * (1 - Throttle) / self.Inertia + local IdleRPM = SelfTbl.IdleRPM + local PeakRPM = IsElectric and SelfTbl.FlywheelOverride or SelfTbl.PeakMaxRPM + local Inertia = SelfTbl.Inertia + local PeakTorque = SelfTbl.PeakTorque + local Drag = PeakTorque * (max(FlyRPM - IdleRPM, 0) / PeakRPM) * (1 - Throttle) / Inertia + + local Torque = 0 + + if Throttle ~= 0 and FlyRPM < LimitRPM then + local Percent = Remap(FlyRPM, IdleRPM, LimitRPM, 0, 1) + Torque = Throttle * ACF.GetTorque(SelfTbl.TorqueCurve, Percent) * PeakTorque -- * (FlyRPM < LimitRPM and 1 or 0) + end + + SelfTbl.Torque = Torque - self.Torque = Throttle * ACF.GetTorque(self.TorqueCurve, Percent) * self.PeakTorque * (self.FlyRPM < self.LimitRPM and 1 or 0) -- Let's accelerate the flywheel based on that torque - self.FlyRPM = min(max(self.FlyRPM + self.Torque / self.Inertia - Drag, 0), self.LimitRPM) + FlyRPM = min(max(FlyRPM + Torque / Inertia - Drag, 0), LimitRPM) -- The gearboxes don't think on their own, it's the engine that calls them, to ensure consistent execution order local Boxes = 0 local TotalReqTq = 0 - -- Get the requirements for torque for the gearboxes (Max clutch rating minus any wheels currently spinning faster than the Flywheel) - for Ent, Link in pairs(self.Gearboxes) do - if not Ent.Disabled then - Boxes = Boxes + 1 - Link.ReqTq = Ent:Calc(self.FlyRPM, self.Inertia) - TotalReqTq = TotalReqTq + Link.ReqTq + -- This is the presently available torque from the engine + local TorqueDiff = max(FlyRPM - IdleRPM, 0) * Inertia + + -- The resulting torque output would be 0 when there's no throttle anyways, so we'll just skip the calculations entirely + if Throttle ~= 0 then + local BoxesTbl = SelfTbl.Gearboxes + + -- Get the requirements for torque for the gearboxes (Max clutch rating minus any wheels currently spinning faster than the Flywheel) + for Ent, Link in pairs(BoxesTbl) do + if not Ent.Disabled then + Boxes = Boxes + 1 + Link.ReqTq = Ent:Calc(FlyRPM, Inertia) + TotalReqTq = TotalReqTq + Link.ReqTq + end end - end - -- This is the presently available torque from the engine - local TorqueDiff = max(self.FlyRPM - self.IdleRPM, 0) * self.Inertia - -- Calculate the ratio of total requested torque versus what's available - local AvailRatio = min(TorqueDiff / TotalReqTq / Boxes, 1) - - -- Split the torque fairly between the gearboxes who need it - for Ent, Link in pairs(self.Gearboxes) do - if not Ent.Disabled then - Ent:Act(Link.ReqTq * AvailRatio * self.MassRatio, DeltaTime, self.MassRatio) + -- Calculate the ratio of total requested torque versus what's available + local AvailRatio = min(TorqueDiff / TotalReqTq / Boxes, 1) + + local MassRatio = SelfTbl.MassRatio + + -- Split the torque fairly between the gearboxes who need it + for Ent, Link in pairs(BoxesTbl) do + Ent:Act(Link.ReqTq * AvailRatio * MassRatio, DeltaTime, MassRatio) end end - self.FlyRPM = self.FlyRPM - min(TorqueDiff, TotalReqTq) / self.Inertia - self.LastThink = Clock.CurTime + SelfTbl.FlyRPM = FlyRPM - min(TorqueDiff, TotalReqTq) / Inertia + SelfTbl.LastThink = ClockTime - self:UpdateSound() - self:UpdateOutputs() + self:UpdateSound(SelfTbl) + self:UpdateOutputs(SelfTbl) - TimerSimple(engine.TickInterval(), function() + TimerSimple(TickInterval(), function() if not IsValid(self) then return end - self:CalcRPM() + self:CalcRPM(SelfTbl) end) end diff --git a/lua/entities/acf_fueltank/init.lua b/lua/entities/acf_fueltank/init.lua index a1f805e3c..a4712c8b6 100644 --- a/lua/entities/acf_fueltank/init.lua +++ b/lua/entities/acf_fueltank/init.lua @@ -16,6 +16,8 @@ local Sounds = Utilities.Sounds local RefillDist = ACF.RefillDistance * ACF.RefillDistance local TimerCreate = timer.Create local TimerExists = timer.Exists +local Clamp = math.Clamp +local Round = math.Round local HookRun = hook.Run local function CanRefuel(Refill, Tank, Distance) @@ -83,9 +85,9 @@ do -- Spawn and Update functions local Max = ACF.FuelMaxSize local Size = Data.Size - Size.x = math.Clamp(math.Round(Size.x), Min, Max) - Size.y = math.Clamp(math.Round(Size.y), Min, Max) - Size.z = math.Clamp(math.Round(Size.z), Min, Max) + Size.x = Clamp(Round(Size.x), Min, Max) + Size.y = Clamp(Round(Size.y), Min, Max) + Size.z = Clamp(Round(Size.z), Min, Max) end else Data.Size = nil @@ -213,11 +215,13 @@ do -- Spawn and Update functions Player:AddCleanup("acf_fueltank", Tank) Player:AddCount(Limit, Tank) - Tank.Owner = Player -- MUST be stored on ent for PP - Tank.Engines = {} - Tank.Leaking = 0 - Tank.LastThink = 0 - Tank.DataStore = Entities.GetArguments("acf_fueltank") + Tank.Owner = Player -- MUST be stored on ent for PP + Tank.Engines = {} + Tank.Leaking = 0 + Tank.LastThink = 0 + Tank.LastFuel = 0 + Tank.LastActivated = 0 + Tank.DataStore = Entities.GetArguments("acf_fueltank") UpdateFuelTank(Tank, Data, Class, FuelTank, FuelType) @@ -410,22 +414,24 @@ function ENT:Disable() end do -- Mass Update - local function UpdateMass(Entity) - local Fuel = Entity.FuelType == "Electric" and Entity.Liters or Entity.Fuel - local Mass = math.floor(Entity.EmptyMass + Fuel * Entity.FuelDensity) + local function UpdateMass(Entity, SelfTbl) + SelfTbl = SelfTbl or Entity:GetTable() + local Fuel = SelfTbl.FuelType == "Electric" and SelfTbl.Liters or SelfTbl.Fuel + local Mass = math.floor(SelfTbl.EmptyMass + Fuel * SelfTbl.FuelDensity) local PhysObj = Entity:GetPhysicsObject() if IsValid(PhysObj) then - Entity.ACF.Mass = Mass - Entity.ACF.LegalMass = Mass + SelfTbl.ACF.Mass = Mass + SelfTbl.ACF.LegalMass = Mass PhysObj:SetMass(Mass) end end - function ENT:UpdateMass(Instant) + function ENT:UpdateMass(Instant, SelfTbl) + SelfTbl = SelfTbl or self:GetTable() if Instant then - return UpdateMass(self) + return UpdateMass(self, SelfTbl) end if TimerExists("ACF Mass Buffer" .. self:EntIndex()) then return end @@ -433,7 +439,7 @@ do -- Mass Update TimerCreate("ACF Mass Buffer" .. self:EntIndex(), 1, 1, function() if not IsValid(self) then return end - UpdateMass(self) + UpdateMass(self, SelfTbl) end) end end @@ -460,18 +466,20 @@ do -- Overlay Update Size = Class.CalcOverlaySize(self) end - local FuelType = FuelTypes.Get(self.FuelType) + local FuelTypeID = self.FuelType + local FuelType = FuelTypes.Get(FuelTypeID) + local Fuel = self.Fuel if FuelType and FuelType.FuelTankOverlayText then - Content = FuelType.FuelTankOverlayText(self.Fuel) + Content = FuelType.FuelTankOverlayText(Fuel) else - local Liters = math.Round(self.Fuel, 2) - local Gallons = math.Round(self.Fuel * 0.264172, 2) + local Liters = Round(Fuel, 2) + local Gallons = Round(Fuel * 0.264172, 2) Content = "Fuel Remaining: " .. Liters .. " liters / " .. Gallons .. " gallons" end - return Text:format(Status, Size, self.FuelType, Content) + return Text:format(Status, Size, FuelTypeID, Content) end end @@ -485,32 +493,48 @@ ACF.AddInputAction("acf_fueltank", "Refuel Duty", function(Entity, Value) Entity.SupplyFuel = tobool(Value) or nil end) -function ENT:CanConsume() - if self.Disabled then return false end - if not self.Active then return false end +function ENT:CanConsume(SelfTbl) + SelfTbl = SelfTbl or self:GetTable() + if SelfTbl.Disabled then return false end + if not SelfTbl.Active then return false end - return self.Fuel > 0 + return SelfTbl.Fuel > 0 end -function ENT:Consume(Amount) - self.Fuel = math.Clamp(self.Fuel - Amount, 0, self.Capacity) +function ENT:Consume(Amount, SelfTbl) + SelfTbl = SelfTbl or self:GetTable() + local Fuel = Clamp(SelfTbl.Fuel - Amount, 0, SelfTbl.Capacity) + SelfTbl.Fuel = Fuel self:UpdateOverlay() - self:UpdateMass() + self:UpdateMass(_, SelfTbl) - WireLib.TriggerOutput(self, "Fuel", self.Fuel) - WireLib.TriggerOutput(self, "Activated", self:CanConsume() and 1 or 0) + Fuel = Round(Fuel, 2) + local Activated = self:CanConsume(SelfTbl) and 1 or 0 + + if SelfTbl.LastFuel ~= Fuel then + SelfTbl.LastFuel = Fuel + WireLib.TriggerOutput(SelfTbl, "Fuel", Fuel) + end + if SelfTbl.LastActivated ~= Activated then + SelfTbl.LastActivated = Activated + WireLib.TriggerOutput(SelfTbl, "Activated", Activated) + end end function ENT:Think() self:NextThink(Clock.CurTime + 1) - if self.Leaking > 0 then - self:Consume(self.Leaking) + local Leaking = self.Leaking - self.Leaking = math.Clamp(self.Leaking - (1 / math.max(self.Fuel, 1)) ^ 0.5, 0, self.Fuel) -- Fuel tanks are self healing + if Leaking > 0 then + self:Consume(Leaking) - WireLib.TriggerOutput(self, "Leaking", self.Leaking > 0 and 1 or 0) + local Fuel = self.Fuel + Leaking = Clamp(Leaking - (1 / math.max(Fuel, 1)) ^ 0.5, 0, Fuel) -- Fuel tanks are self healing + self.Leaking = Leaking + + WireLib.TriggerOutput(self, "Leaking", Leaking > 0 and 1 or 0) self:NextThink(Clock.CurTime + 0.25) end diff --git a/lua/entities/acf_gearbox/init.lua b/lua/entities/acf_gearbox/init.lua index 8c7a9a220..3f8bff0e0 100644 --- a/lua/entities/acf_gearbox/init.lua +++ b/lua/entities/acf_gearbox/init.lua @@ -11,19 +11,22 @@ local ACF = ACF local Utilities = ACF.Utilities local Clock = Utilities.Clock local Clamp = math.Clamp +local abs = math.abs +local min = math.min local HookRun = hook.Run local function CalcWheel(Entity, Link, Wheel, SelfWorld) local WheelPhys = Wheel:GetPhysicsObject() local VelDiff = WheelPhys:LocalToWorldVector(WheelPhys:GetAngleVelocity()) - SelfWorld local BaseRPM = VelDiff:Dot(WheelPhys:LocalToWorldVector(Link.Axis)) + local GearRatio = Entity.GearRatio Link.Vel = BaseRPM - if Entity.GearRatio == 0 then return 0 end + if 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 / Entity.GearRatio / -6 + return BaseRPM / GearRatio / -6 end do -- Spawn and Update functions ----------------------- @@ -733,6 +736,7 @@ do -- Gear Shifting ------------------------------------ end ---------------------------------------------------- do -- Movement ----------------------------------------- + local deg = math.deg local Contraption = ACF.Contraption local function ActWheel(Link, Wheel, Torque, DeltaTime) @@ -742,7 +746,7 @@ do -- Movement ----------------------------------------- local TorqueAxis = Phys:LocalToWorldVector(Link.Axis) - Phys:ApplyTorqueCenter(TorqueAxis * Clamp(math.deg(-Torque * 1.5) * DeltaTime, -500000, 500000)) + Phys:ApplyTorqueCenter(TorqueAxis * Clamp(deg(-Torque * 1.5) * DeltaTime, -500000, 500000)) end function ENT:Calc(InputRPM, InputInertia) @@ -755,12 +759,14 @@ do -- Movement ----------------------------------------- local BoxPhys = Contraption.GetAncestor(self):GetPhysicsObject() local SelfWorld = BoxPhys:LocalToWorldVector(BoxPhys:GetAngleVelocity()) + local Gear = self.Gear - if self.CVT and self.Gear == 1 then + if self.CVT and Gear == 1 then if self.CVTRatio > 0 then self.Gears[1] = Clamp(self.CVTRatio, 0.01, 1) else - self.Gears[1] = Clamp((InputRPM - self.MinRPM) / (self.MaxRPM - self.MinRPM), 0.05, 1) + local MinRPM = self.MinRPM + self.Gears[1] = Clamp((InputRPM - MinRPM) / (self.MaxRPM - MinRPM), 0.05, 1) end self.GearRatio = self.Gears[1] * self.FinalDrive @@ -771,40 +777,42 @@ do -- Movement ----------------------------------------- if self.Automatic and self.Drive == 1 and self.InGear then local PhysVel = BoxPhys:GetVelocity():Length() - if not self.Hold and self.Gear ~= self.MaxGear and PhysVel > (self.ShiftPoints[self.Gear] * self.ShiftScale) then - self:ChangeGear(self.Gear + 1) - elseif PhysVel < (self.ShiftPoints[self.Gear - 1] * self.ShiftScale) then - self:ChangeGear(self.Gear - 1) + if not self.Hold and Gear ~= self.MaxGear and PhysVel > (self.ShiftPoints[Gear] * self.ShiftScale) then + self:ChangeGear(Gear + 1) + elseif PhysVel < (self.ShiftPoints[Gear - 1] * self.ShiftScale) then + self:ChangeGear(Gear - 1) end end - self.TotalReqTq = 0 - self.TorqueOutput = 0 + local TorqueOutput = 0 + local TotalReqTq = 0 + local LClutch = self.LClutch + local RClutch = self.RClutch + local GearRatio = self.GearRatio for Ent, Link in pairs(self.GearboxOut) do - local Clutch = Link.Side == 0 and self.LClutch or self.RClutch + local Clutch = Link.Side == 0 and LClutch or RClutch Link.ReqTq = 0 if not Ent.Disabled then local Inertia = 0 - if self.GearRatio ~= 0 then - Inertia = InputInertia / self.GearRatio + if GearRatio ~= 0 then + Inertia = InputInertia / GearRatio end - Link.ReqTq = math.abs(Ent:Calc(InputRPM * self.GearRatio, Inertia) * self.GearRatio) * Clutch - self.TotalReqTq = self.TotalReqTq + math.abs(Link.ReqTq) + Link.ReqTq = abs(Ent:Calc(InputRPM * GearRatio, Inertia) * GearRatio) * Clutch + TotalReqTq = TotalReqTq + abs(Link.ReqTq) end end for Wheel, Link in pairs(self.Wheels) do - local RPM = CalcWheel(self, Link, Wheel, SelfWorld) - Link.ReqTq = 0 - if self.GearRatio ~= 0 then - local Clutch = Link.Side == 0 and self.LClutch or self.RClutch + if GearRatio ~= 0 then + local RPM = CalcWheel(self, Link, Wheel, SelfWorld) + local Clutch = Link.Side == 0 and LClutch or RClutch local OnRPM = ((InputRPM > 0 and RPM < InputRPM) or (InputRPM < 0 and RPM > InputRPM)) if Clutch > 0 and OnRPM then @@ -815,7 +823,7 @@ do -- Movement ----------------------------------------- -- this actually controls the RPM of the wheels, so the steering rate is correct if Link.Side == 0 then - Multiplier = math.min(0, Rate) + 1 + Multiplier = min(0, Rate) + 1 else Multiplier = -math.max(0, Rate) + 1 end @@ -823,38 +831,48 @@ do -- Movement ----------------------------------------- Link.ReqTq = (InputRPM * Multiplier - RPM) * InputInertia * Clutch - self.TotalReqTq = self.TotalReqTq + math.abs(Link.ReqTq) + TotalReqTq = TotalReqTq + abs(Link.ReqTq) end end end - self.TorqueOutput = math.min(self.TotalReqTq, self.MaxTorque) + self.TotalReqTq = TotalReqTq + TorqueOutput = min(TotalReqTq, self.MaxTorque) + self.TorqueOutput = TorqueOutput self:UpdateOverlay() - return self.TorqueOutput + return TorqueOutput end function ENT:Act(Torque, DeltaTime, MassRatio) if self.Disabled then return end + if Torque == 0 then + self.LastActive = Clock.CurTime + return + end + local Loss = Clamp(((1 - 0.4) / 0.5) * ((self.ACF.Health / self.ACF.MaxHealth) - 1) + 1, 0.4, 1) --internal torque loss from damaged local Slop = self.Automatic and 0.9 or 1 --internal torque loss from inefficiency 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 + local GearRatio = self.GearRatio - if Torque ~= 0 and self.GearRatio ~= 0 then - AvailTq = math.min(math.abs(Torque) / self.TotalReqTq, 1) / self.GearRatio * -(-Torque / math.abs(Torque)) * Loss * Slop + if Torque ~= 0 and GearRatio ~= 0 then + AvailTq = min(abs(Torque) / self.TotalReqTq, 1) / GearRatio * -(-Torque / abs(Torque)) * Loss * Slop end for Ent, Link in pairs(self.GearboxOut) do Ent:Act(Link.ReqTq * AvailTq, DeltaTime, MassRatio) end + local Braking = self.Braking + for Ent, Link in pairs(self.Wheels) do -- If the gearbox is braking, always - if not self.Braking or not Link.IsBraking then + if not Braking or not Link.IsBraking then local WheelTorque = Link.ReqTq * AvailTq ReactTq = ReactTq + WheelTorque @@ -866,7 +884,7 @@ do -- Movement ----------------------------------------- local BoxPhys = Contraption.GetAncestor(self):GetPhysicsObject() if IsValid(BoxPhys) then - BoxPhys:ApplyTorqueCenter(self:GetRight() * Clamp(2 * math.deg(ReactTq * MassRatio) * DeltaTime, -500000, 500000)) + BoxPhys:ApplyTorqueCenter(self:GetRight() * Clamp(2 * deg(ReactTq * MassRatio) * DeltaTime, -500000, 500000)) end end @@ -884,12 +902,12 @@ do -- Braking ------------------------------------------ if not Phys:IsMotionEnabled() then return end -- skipping entirely if its frozen if Brake > 100 then - local Overshot = math.abs(Link.LastVel - Link.Vel) > math.abs(Link.LastVel) -- Overshot the brakes last tick? + local Overshot = abs(Link.LastVel - Link.Vel) > abs(Link.LastVel) -- Overshot the brakes last tick? local Rate = Overshot and 0.2 or 0.002 -- If we overshot, cut back agressively, if we didn't, add more brakes slowly Link.AntiSpazz = (1 - Rate) * Link.AntiSpazz + (Overshot and 0 or Rate) -- Low pass filter on the antispazz - AntiSpazz = math.min(Link.AntiSpazz * 10000 / Brake, 1) -- Anti-spazz relative to brake power + AntiSpazz = min(Link.AntiSpazz * 10000 / Brake, 1) -- Anti-spazz relative to brake power end Link.LastVel = Link.Vel