This commit is contained in:
Никита Круглицкий 2025-05-11 08:18:33 +06:00
parent 14ab069846
commit a6c89e2f21
13 changed files with 229 additions and 51 deletions

View File

@ -1,6 +1,5 @@
--@name SX 240 --@name SX 240
--@author Koptilnya --@author Koptilnya
--@server
--@include /koptilnya/engine_remastered/vehicle.txt --@include /koptilnya/engine_remastered/vehicle.txt
--@include /koptilnya/engine_remastered/powertrain/differential.txt --@include /koptilnya/engine_remastered/powertrain/differential.txt
@ -8,6 +7,7 @@ local Vehicle, POWERTRAIN_COMPONENT = unpack(require('/koptilnya/engine_remaster
local Differential = require('/koptilnya/engine_remastered/powertrain/differential.txt') local Differential = require('/koptilnya/engine_remastered/powertrain/differential.txt')
local WheelConfig = { local WheelConfig = {
DEBUG = true,
BrakePower = 1200, BrakePower = 1200,
CustomWheel = { Mass = 80 }, CustomWheel = { Mass = 80 },
Model = 'models/sprops/trans/wheel_a/wheel25.mdl' Model = 'models/sprops/trans/wheel_a/wheel25.mdl'
@ -25,7 +25,7 @@ Vehicle:new({
Inertia = 0.151, Inertia = 0.151,
StartFriction = -10, StartFriction = -10,
FrictionCoeff = 0.01, FrictionCoeff = 0.01,
LimiterDuration = 0.08, LimiterDuration = 0.06,
TorqueMap = { TorqueMap = {
118.89918444138, 122.0751393736, 125.25109430583, 128.42704923806, 131.60300417029, 134.77895910251, 137.95491403474, 141.13086896697, 144.3068238992, 147.48277883143, 150.65873376365, 153.83468869588, 157.01064362811, 160.18659856034, 163.36255349256, 118.89918444138, 122.0751393736, 125.25109430583, 128.42704923806, 131.60300417029, 134.77895910251, 137.95491403474, 141.13086896697, 144.3068238992, 147.48277883143, 150.65873376365, 153.83468869588, 157.01064362811, 160.18659856034, 163.36255349256,
166.53850842479, 169.71446335702, 172.89041828925, 176.06637322148, 179.2423281537, 182.41828308593, 185.59423801816, 188.77019295039, 191.94614788261, 195.12210281484, 198.29805774707, 201.4740126793, 204.64996761153, 207.82592254375, 211.00187747598, 166.53850842479, 169.71446335702, 172.89041828925, 176.06637322148, 179.2423281537, 182.41828308593, 185.59423801816, 188.77019295039, 191.94614788261, 195.12210281484, 198.29805774707, 201.4740126793, 204.64996761153, 207.82592254375, 211.00187747598,

View File

@ -10,6 +10,8 @@ local Clutch = class('Clutch', PowertrainComponent)
function Clutch:initialize(vehicle, name, config) function Clutch:initialize(vehicle, name, config)
PowertrainComponent.initialize(self, vehicle, name, config) PowertrainComponent.initialize(self, vehicle, name, config)
if CLIENT then return end
self.wireInputs = { self.wireInputs = {
Clutch = 'number' Clutch = 'number'
} }
@ -22,6 +24,8 @@ function Clutch:initialize(vehicle, name, config)
end end
function Clutch:updateWireOutputs() function Clutch:updateWireOutputs()
PowertrainComponent.updateWireOutputs(self)
wire.ports.Clutch_Torque = self.torque wire.ports.Clutch_Torque = self.torque
end end

View File

@ -15,6 +15,8 @@ end
function Differential:initialize(vehicle, name, config) function Differential:initialize(vehicle, name, config)
PowertrainComponent.initialize(self, vehicle, name, config) PowertrainComponent.initialize(self, vehicle, name, config)
if CLIENT then return end
self.wireOutputs = { self.wireOutputs = {
Diff_Torque = 'number' Diff_Torque = 'number'
} }
@ -48,6 +50,8 @@ function Differential:linkComponent(component)
end end
function Differential:updateWireOutputs() function Differential:updateWireOutputs()
PowertrainComponent.updateWireOutputs(self)
wire.ports.Diff_Torque = self.torque wire.ports.Diff_Torque = self.torque
if self.outputA ~= nil then if self.outputA ~= nil then

View File

@ -3,6 +3,8 @@
local PowertrainComponent = require('./powertrain_component.txt') local PowertrainComponent = require('./powertrain_component.txt')
require('/koptilnya/libs/constants.txt') require('/koptilnya/libs/constants.txt')
local Engine = class('Engine', PowertrainComponent) local Engine = class('Engine', PowertrainComponent)
@ -38,9 +40,18 @@ function Engine:initialize(vehicle, name, config)
self.reactTorque = 0 self.reactTorque = 0
self.returnedTorque = 0 self.returnedTorque = 0
if CLIENT then
--@include /koptilnya/engine_sound_2.txt
local Sound = require('/koptilnya/engine_sound_2.txt')
Sound(config.MaxRPM or 7000, chip())
end
end end
function Engine:updateWireOutputs() function Engine:updateWireOutputs()
PowertrainComponent.updateWireOutputs(self)
wire.ports.Engine_RPM = self:getRPM() wire.ports.Engine_RPM = self:getRPM()
wire.ports.Engine_Torque = self.torque wire.ports.Engine_Torque = self.torque
wire.ports.Engine_MasterThrottle = self.masterThrottle wire.ports.Engine_MasterThrottle = self.masterThrottle
@ -97,7 +108,7 @@ function Engine:forwardStep()
local generatedTorque = self:_generateTorque() local generatedTorque = self:_generateTorque()
local reactTorque = (targetW - self.angularVelocity) * self.inertia / TICK_INTERVAL local reactTorque = (targetW - self.angularVelocity) * self.inertia / TICK_INTERVAL
local returnedTorque = self.output:forwardStep(generatedTorque - reactTorque, 0) -- ??? 0 -> self.inertia local returnedTorque = self.output:forwardStep(generatedTorque - reactTorque, self.inertia) -- ??? 0 -> self.inertia
self.reactTorque = reactTorque self.reactTorque = reactTorque
self.returnedTorque = returnedTorque self.returnedTorque = returnedTorque
@ -108,6 +119,11 @@ function Engine:forwardStep()
self.angularVelocity = self.angularVelocity + finalTorque / inertiaSum * TICK_INTERVAL self.angularVelocity = self.angularVelocity + finalTorque / inertiaSum * TICK_INTERVAL
self.angularVelocity = math.max(self.angularVelocity, 0) self.angularVelocity = math.max(self.angularVelocity, 0)
net.start("ENGINE_FULLRPM")
net.writeUInt(self:getRPM(), 16)
net.writeFloat(self.masterThrottle)
net.send(nil, true)
end end
return Engine return Engine

View File

@ -7,6 +7,8 @@ local Gearbox = class('Gearbox', PowertrainComponent)
function Gearbox:initialize(vehicle, name, config) function Gearbox:initialize(vehicle, name, config)
PowertrainComponent.initialize(self, vehicle, name, config) PowertrainComponent.initialize(self, vehicle, name, config)
if CLIENT then return end
self.wireInputs = { self.wireInputs = {
Upshift = 'number', Upshift = 'number',
Downshift = 'number' Downshift = 'number'
@ -24,6 +26,8 @@ function Gearbox:initialize(vehicle, name, config)
end end
function Gearbox:updateWireOutputs() function Gearbox:updateWireOutputs()
PowertrainComponent.updateWireOutputs(self)
wire.ports.Gearbox_RPM = self:getRPM() wire.ports.Gearbox_RPM = self:getRPM()
wire.ports.Gearbox_Torque = self.torque wire.ports.Gearbox_Torque = self.torque
wire.ports.Gearbox_Ratio = self.ratio wire.ports.Gearbox_Ratio = self.ratio

View File

@ -10,6 +10,8 @@ local ManualGearbox = class('ManualGearbox', BaseGearbox)
function ManualGearbox:initialize(vehicle, name, config) function ManualGearbox:initialize(vehicle, name, config)
BaseGearbox.initialize(self, vehicle, name, config) BaseGearbox.initialize(self, vehicle, name, config)
if CLIENT then return end
table.merge(self.wireOutputs, { table.merge(self.wireOutputs, {
Gearbox_Gear = 'number' Gearbox_Gear = 'number'
}) })

View File

@ -1,9 +1,11 @@
--@include /koptilnya/libs/constants.txt --@include /koptilnya/libs/constants.txt
--@include /koptilnya/libs/utils.txt
--@include ../wire_component.txt --@include ../wire_component.txt
local WireComponent = require('../wire_component.txt') local WireComponent = require('../wire_component.txt')
require('/koptilnya/libs/constants.txt') require('/koptilnya/libs/constants.txt')
require('/koptilnya/libs/utils.txt')
local PowertrainComponent = class('PowertrainComponent', WireComponent) local PowertrainComponent = class('PowertrainComponent', WireComponent)
@ -15,12 +17,29 @@ function PowertrainComponent:initialize(vehicle, name, config)
self.vehicle = vehicle self.vehicle = vehicle
self.name = name or 'PowertrainComponent' self.name = name or 'PowertrainComponent'
self.CONFIG = config self.CONFIG = config
self.DEBUG = config.DEBUG or false
self.input = nil self.input = nil
self.output = nil self.output = nil
self.inertia = 0.02 self.inertia = 0.02
self.angularVelocity = 0 self.angularVelocity = 0
self.torque = 0 self.torque = 0
self.DEBUG_DATA = {}
if self.DEBUG then
if CLIENT then
net.receive('DEBUG_' .. self.name, function()
self.DEBUG_DATA = net.readTable()
end)
end
self.DEBUG_SEND_DATA_DEBOUNCED = debounce(function()
net.start("DEBUG_" .. self.name)
net.writeTable(self.DEBUG_DATA)
net.send(nil, true)
end, TICK_INTERVAL * 20)
end
end end
function PowertrainComponent:start() function PowertrainComponent:start()
@ -67,4 +86,12 @@ function PowertrainComponent:forwardStep(torque, inertia)
return self.output:forwardStep(self.torque, self.inertia + inertia) return self.output:forwardStep(self.torque, self.inertia + inertia)
end end
function PowertrainComponent:updateWireOutputs()
WireComponent.updateWireOutputs(self)
if self.DEBUG then
self.DEBUG_SEND_DATA_DEBOUNCED()
end
end
return PowertrainComponent return PowertrainComponent

View File

@ -16,12 +16,40 @@ local DEBUG = false
function Wheel:initialize(vehicle, name, config) function Wheel:initialize(vehicle, name, config)
PowertrainComponent.initialize(self, vehicle, name, config) PowertrainComponent.initialize(self, vehicle, name, config)
if CLIENT and self.DEBUG then
local scale = 0.1
local font = render.createFont("Roboto", 256, 400, true)
local mat = render.createMaterial('models/debug/debugwhite')
hook.add("PostDrawTranslucentRenderables", "DEBUG_RENDER_" .. self.name, function()
if next(self.DEBUG_DATA) == nil then return end
if not isValid(self.DEBUG_DATA.entity) then return end
local pos = self.DEBUG_DATA.entity:getPos()
render.setMaterial(mat)
render.setColor(Color(255, 0, 0, 200))
render.draw3DBeam(pos, pos + (16 * self.DEBUG_DATA.forward), 1, 0, 0)
render.setColor(Color(0, 255, 0, 255))
render.draw3DBeam(pos, pos + (16 * self.DEBUG_DATA.right), 1, 0, 0)
render.setColor(Color(0, 0, 255, 200))
render.draw3DBeam(pos, pos + (16 * self.DEBUG_DATA.up), 1, 0, 0)
end)
if player() == owner() then
enableHud(nil, true)
end
return
end
self.steerLock = config.SteerLock or 0 self.steerLock = config.SteerLock or 0
self.brakePower = config.BrakePower or 0 self.brakePower = config.BrakePower or 0
self.handbrakePower = config.HandbrakePower or 0 self.handbrakePower = config.HandbrakePower or 0
self.rotationAxle = config.RotationAxle or Angle(0, 0, 1)
self.wireInputs = { self.wireInputs = {
[self.name] = 'entity' [self.name] = 'entity'
} }
@ -50,14 +78,8 @@ function Wheel:initialize(vehicle, name, config)
self.holo = self:createHolo(self.entity) self.holo = self:createHolo(self.entity)
if DEBUG then if self.DEBUG then
self.debugHoloF:setPos(self.entity:getPos()) self.DEBUG_DATA.entity = self.entity
self.debugHoloR:setPos(self.entity:getPos())
self.debugHoloU:setPos(self.entity:getPos())
self.debugHoloF:setParent(self.entity)
self.debugHoloR:setParent(self.entity)
self.debugHoloU:setParent(self.entity)
end end
if not config.Radius then if not config.Radius then
@ -66,20 +88,6 @@ function Wheel:initialize(vehicle, name, config)
end end
end) end)
if DEBUG then
self.debugHoloF = holograms.create(Vector(), Angle(), 'models/sprops/rectangles_thin/size_0/rect_1_5x48x1_5.mdl')
self.debugHoloR = holograms.create(Vector(), Angle(), 'models/sprops/rectangles_thin/size_0/rect_1_5x48x1_5.mdl')
self.debugHoloU = holograms.create(Vector(), Angle(), 'models/sprops/rectangles_thin/size_0/rect_1_5x48x1_5.mdl')
self.debugHoloF:setMaterial('models/debug/debugwhite')
self.debugHoloR:setMaterial('models/debug/debugwhite')
self.debugHoloU:setMaterial('models/debug/debugwhite')
self.debugHoloF:setColor(Color(255, 0, 0))
self.debugHoloR:setColor(Color(0, 255, 0))
self.debugHoloU:setColor(Color(0, 0, 255))
end
self.steerVelocity = 0 self.steerVelocity = 0
end end
@ -110,6 +118,8 @@ function Wheel:createHolo(entity)
end end
function Wheel:updateWireOutputs() function Wheel:updateWireOutputs()
PowertrainComponent.updateWireOutputs(self)
wire.ports[string.format('%s_RPM', self.name)] = self.customWheel:getRPM() wire.ports[string.format('%s_RPM', self.name)] = self.customWheel:getRPM()
wire.ports[string.format('%s_Mz', self.name)] = self.customWheel.mz wire.ports[string.format('%s_Mz', self.name)] = self.customWheel.mz
wire.ports[string.format('%s_Fz', self.name)] = self.customWheel.load wire.ports[string.format('%s_Fz', self.name)] = self.customWheel.load
@ -145,10 +155,10 @@ function Wheel:forwardStep(torque, inertia)
end end
if isValid(self.holo) then if isValid(self.holo) then
if DEBUG then if self.DEBUG then
self.debugHoloF:setAngles(self.customWheel.forward:getAngle()) self.DEBUG_DATA.forward = self.customWheel.forward
self.debugHoloR:setAngles(self.customWheel.right:getAngle()) self.DEBUG_DATA.right = self.customWheel.right
self.debugHoloU:setAngles(self.customWheel.up:getAngle()) self.DEBUG_DATA.up = self.customWheel.up
end end
local entityAngles = self.entity:getAngles() local entityAngles = self.entity:getAngles()

View File

@ -1,8 +1,9 @@
--@server
--@include ./enums/powertrain_component.txt --@include ./enums/powertrain_component.txt
--@include ./factories/powertrain_component.txt --@include ./factories/powertrain_component.txt
--@include /koptilnya/libs/table.txt --@include /koptilnya/libs/table.txt
--@include /koptilnya/libs/constants.txt --@include /koptilnya/libs/constants.txt
--@include /libs/task.txt
local Task = require('/libs/task.txt')
local PowertrainComponentFactory = require('./factories/powertrain_component.txt') local PowertrainComponentFactory = require('./factories/powertrain_component.txt')
local CustomWheel = require('./wheel/wheel.txt') local CustomWheel = require('./wheel/wheel.txt')
@ -25,16 +26,22 @@ function Vehicle:initialize(config)
self:createComponents() self:createComponents()
self:linkComponents() self:linkComponents()
self:createIO()
if SERVER then
self:createIO()
end
self.rootComponent = self:getRootComponent() self.rootComponent = self:getRootComponent()
self.steer = 0
self.brake = 0
self.handbrake = 0
hook.add('input', 'vehicle_wire_input', function(name, value) if SERVER then
self:handleWireInput(name, value) self.steer = 0
end) self.brake = 0
self.handbrake =
hook.add('input', 'vehicle_wire_input', function(name, value)
self:handleWireInput(name, value)
end)
end
hook.add('tick', 'vehicle_update', function() hook.add('tick', 'vehicle_update', function()
self:update() self:update()
@ -112,16 +119,18 @@ function Vehicle:handleWireInput(name, value)
end end
function Vehicle:update() function Vehicle:update()
local base = wire.ports.Base if SERVER then
local base = wire.ports.Base
self.rootComponent:forwardStep() self.rootComponent:forwardStep()
for _, component in pairs(self.independentComponents) do for _, component in pairs(self.independentComponents) do
component:forwardStep(0, component:queryInertia()) component:forwardStep(0, component:queryInertia())
end end
for _, component in pairs(self.components) do for _, component in pairs(self.components) do
component:updateWireOutputs() component:updateWireOutputs()
end
end end
--if isValid(base) and (self.clutch:getPress() == 1 or self.gearbox.ratio == 0) then --if isValid(base) and (self.clutch:getPress() == 1 or self.gearbox.ratio == 0) then

View File

@ -1,6 +1,8 @@
local WireComponent = class('WireComponent') local WireComponent = class('WireComponent')
function WireComponent:initialize() function WireComponent:initialize()
if CLIENT then return end
self.wireInputs = {} self.wireInputs = {}
self.wireOutputs = {} self.wireOutputs = {}
end end

View File

@ -1,5 +1,5 @@
-- @include libs/utils.txt --@include libs/utils.txt
-- @client --@client
require("/koptilnya/libs/utils.txt") require("/koptilnya/libs/utils.txt")
EngineSound = class("EngineSound") EngineSound = class("EngineSound")

View File

@ -0,0 +1,89 @@
--@client
--@include /libs/task.txt
local Task = require('/libs/task.txt')
local Sound = class("Sound")
local function map(x, a, b, c, d)
return (x - a) / (b - a) * (d - c) + c
end
local function fade(n, min, mid, max)
if n < min or n > max then
return 0
end
if n > mid then
min = mid - (max - mid)
end
return math.cos((1 - ((n - min) / (mid - min))) * (math.pi / 2))
end
function Sound:initialize(redline, parent, sounds)
local sounds = sounds or {
[900] = "https://raw.githubusercontent.com/koptilnya/gmod-data/main/engine_sounds/bmw_s54/ext_e30s54_idle.ogg",
[2500] = "https://raw.githubusercontent.com/koptilnya/gmod-data/main/engine_sounds/bmw_s54/ext_e30s54_on_2500.ogg",
[4000] = "https://raw.githubusercontent.com/koptilnya/gmod-data/main/engine_sounds/bmw_s54/ext_e30s54_on_4000.ogg",
[6750] = "https://raw.githubusercontent.com/koptilnya/gmod-data/main/engine_sounds/bmw_s54/ext_e30s54_on_6750.ogg",
[8500] = "https://raw.githubusercontent.com/koptilnya/gmod-data/main/engine_sounds/bmw_s54/ext_e30s54_on_8500.ogg"
}
local redline = redline or 7000
self.active = true
local soundObjects = {}
local soundRpms = {}
local maxValue = 0
local throttle = 0
local engineRpm = 0
local smoothRpm = 0
local smoothThrottle = 0
Task.run(function()
for soundRpm, soundPath in pairs(sounds) do
local sound = await* soundLoad(soundPath, "3d noblock noplay")
soundObjects[soundRpm] = sound
table.insert(soundRpms,soundRpm)
if maxValue < soundRpm then
maxValue = soundRpm
end
end
table.sort(soundRpms)
hook.add("think", table.address({}), function()
if not self.active then
return
end
smoothRpm = smoothRpm * (1 - 0.2) + engineRpm * 0.2
smoothThrottle = smoothThrottle * (1 - 0.1) + throttle * 0.1
for n, rpm in ipairs(soundRpms) do
if not soundObjects[rpm] then
goto CONTINUE
end
local min = n == 1 and -100000 or soundRpms[n - 1]
local max = n == #soundRpms and 100000 or soundRpms[n + 1]
local c = fade(smoothRpm, min - 10, rpm, max + 10)
local vol = c * map(smoothThrottle, 0, 1, 0.5, 1)
local soundObject = soundObjects[rpm].Bass
soundObject:setVolume(vol)
soundObject:setPitch(smoothRpm / rpm)
soundObject:setPos(parent:getPos())
soundObject:pause()
soundObject:play()
::CONTINUE::
end
end)
end)
net.receive("ENGINE_FULLRPM", function()
local rpm = net.readUInt(16)
engineRpm = rpm * (maxValue / redline)
throttle = math.max(net.readFloat(), 0)
end)
end
return Sound

View File

@ -73,3 +73,14 @@ function isURL(str)
return prefix == "http" or prefix == "https" or prefix == "data" return prefix == "http" or prefix == "https" or prefix == "data"
end end
function debounce(func, delay)
local lastCall = 0
return function(...)
local now = timer.systime()
if now - lastCall >= delay then
lastCall = now
return func(...)
end
end
end