2025-05-16 07:11:48 +06:00

168 lines
4.9 KiB
Lua

--@include ./powertrain_component.txt
--@include /koptilnya/libs/constants.txt
local PowertrainComponent = require('./powertrain_component.txt')
require('/koptilnya/libs/constants.txt')
---@class EngineConfig: PowertrainComponentConfig
---@field IdleRPM? number
---@field MaxRPM? number
---@field StartFriction? number
---@field FrictionCoeff? number
---@field LimiterDuration? number
---@field TorqueMap? number[]
---@class Engine: PowertrainComponent
---@field idleRPM number
---@field maxRPM number
---@field startFriction number
---@field frictionCoeff number
---@field limiterDuration number
---@field torqueMap number[]
---@field friction number
---@field masterThrottle number
---@field finalTorque number
---@field reactTorque number
---@field returnedTorque number
---@field private limiterTimer number
local Engine = class('Engine', PowertrainComponent)
---@private
---@param vehicle KPTLVehicle
---@param name string
---@param config EngineConfig
function Engine:initialize(vehicle, name, config)
PowertrainComponent.initialize(self, vehicle, name, config)
self.wireInputs = {
Throttle = 'number',
}
self.wireOutputs = {
Engine_RPM = 'number',
Engine_Torque = 'number',
Engine_MasterThrottle = 'number',
Engine_ReactTorque = 'number',
Engine_ReturnedTorque = 'number',
Engine_FinalTorque = 'number',
}
self.idleRPM = config.IdleRPM or 900
self.maxRPM = config.MaxRPM or 7000
self.inertia = config.Inertia or 0.4
self.startFriction = config.StartFriction or -50
self.frictionCoeff = config.FrictionCoeff or 0.02
self.limiterDuration = config.LimiterDuration or 0.05
self.torqueMap = config.TorqueMap or {}
self.friction = 0
self.masterThrottle = 0
self.limiterTimer = 0
self.finalTorque = 0
self.reactTorque = 0
self.returnedTorque = 0
if CLIENT then
-- --@include /koptilnya/engine_sound_2.txt
-- local Sound = require('/koptilnya/engine_sound_2.txt')
-- Sound(self.maxRPM, chip())
end
end
---@return nil
function Engine:updateWireOutputs()
PowertrainComponent.updateWireOutputs(self)
wire.ports.Engine_RPM = self:getRPM()
wire.ports.Engine_Torque = self.torque
wire.ports.Engine_MasterThrottle = self.masterThrottle
wire.ports.Engine_ReactTorque = self.reactTorque
wire.ports.Engine_ReturnedTorque = self.returnedTorque
wire.ports.Engine_FinalTorque = self.finalTorque
end
---@return number
function Engine:getThrottle()
return wire.ports.Throttle
end
---@private
---@return number
function Engine:generateTorque()
local throttle = self:getThrottle()
local rpm = self:getRPM()
local rpmFrac = math.clamp((rpm - self.idleRPM) / (self.maxRPM - self.idleRPM), 0, 1)
self.friction = self.startFriction - self:getRPM() * self.frictionCoeff
local tqIdx = math.clamp(math.floor(rpmFrac * #self.torqueMap), 1, #self.torqueMap)
local maxInitialTorque = self.torqueMap[tqIdx] - self.friction
local idleFadeStart = math.clamp(math.remap(rpm, self.idleRPM - 300, self.idleRPM, 1, 0), 0, 1)
local idleFadeEnd = math.clamp(math.remap(rpm, self.idleRPM, self.idleRPM + 600, 1, 0), 0, 1)
local additionalEnergySupply = idleFadeEnd * (-self.friction / maxInitialTorque) + idleFadeStart
if rpm > self.maxRPM then
throttle = 0
self.limiterTimer = timer.systime()
else
throttle = timer.systime() >= self.limiterTimer + self.limiterDuration and throttle or 0
end
self.masterThrottle = math.clamp(additionalEnergySupply + throttle, 0, 1)
local realInitialTorque = maxInitialTorque * self.masterThrottle
self.torque = realInitialTorque + self.friction
return self.torque
end
---@param torque number
---@param inertia number
---@return number
function Engine:forwardStep(torque, inertia)
if self.output == nil then
local generatedTorque = self:generateTorque()
self.angularVelocity = self.angularVelocity + generatedTorque / self.inertia * TICK_INTERVAL
self.angularVelocity = math.max(self.angularVelocity, 0)
return 0
end
local outputInertia = self.output:queryInertia()
local inertiaSum = self.inertia + outputInertia
local outputW = self.output:queryAngularVelocity(self.angularVelocity)
local targetW = self.inertia / inertiaSum * self.angularVelocity + outputInertia / inertiaSum * outputW
local generatedTorque = self:generateTorque()
local reactTorque = (targetW - self.angularVelocity) * self.inertia / TICK_INTERVAL
local returnedTorque = self.output:forwardStep(generatedTorque - reactTorque, 0)
self.reactTorque = reactTorque
self.returnedTorque = returnedTorque
local finalTorque = generatedTorque + reactTorque + returnedTorque
self.finalTorque = finalTorque
self.angularVelocity = self.angularVelocity + finalTorque / inertiaSum * TICK_INTERVAL
self.angularVelocity = math.max(self.angularVelocity, 0)
-- net.start('ENGINE_RPM')
-- net.writeFloat(self:getRPM() / self.maxRPM)
-- net.send(self.vehicle.playersConnectedToHUD, true)
-- net.start('ENGINE_FULLRPM')
-- net.writeUInt(self:getRPM(), 16)
-- net.writeFloat(self.masterThrottle)
-- net.send(nil, true)
return 0
end
return Engine