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