--@include ./enums/powertrain_component.txt --@include ./factories/powertrain_component.txt --@include /koptilnya/libs/table.txt --@include /koptilnya/libs/constants.txt --@include /libs/task.txt local Task = require('/libs/task.txt') local PowertrainComponentFactory = require('./factories/powertrain_component.txt') local POWERTRAIN_COMPONENT = require('./enums/powertrain_component.txt') require('/koptilnya/libs/table.txt') require('/koptilnya/libs/constants.txt') ---@class KPTLVehicle ---@field config { Name: string, Input?: string, Config?: table }[] ---@field components PowertrainComponent[] ---@field headComponents PowertrainComponent[] ---@field steer number ---@field brake number ---@field handbrake number ---@field playersConnectedToHUD Player[] ---@field base? Entity ---@field basePhysObject? PhysObj ---@field initialized boolean ---@field initializedPlayers Player[] local Vehicle = class('Vehicle') ---@param config { [string]: any } function Vehicle:initialize(config) if not Vehicle:validateConfig(config) then throw('Please check your vehicle configuration!') end self.initialized = false self.config = config self.components = {} self.headComponents = {} self.steer = 0 self.brake = 0 self.handbrake = 0 self.initializedPlayers = {} self.playersConnectedToHUD = {} if SERVER then self.playersConnectedToHUD = find.allPlayers(function(ply) return isValid(ply) and ply:isHUDActive() end) hook.add('Input', 'vehicle_wire_input', function(name, value) self:handleWireInput(name, value) end) ---@diagnostic disable-next-line: param-type-mismatch hook.add('Tick', 'vehicle_update', function() self:update() end) hook.add('HUDConnected', 'vehicle_hudconnected', function(ent, ply) table.insert(self.playersConnectedToHUD, ply) end) hook.add('HUDDisconnected', 'vehicle_huddisconnected', function(ent, ply) table.removeByValue(self.playersConnectedToHUD, ply) end) hook.add('PlayerDisconnected', 'vehicle_huddisconnected', function(ply) table.removeByValue(self.playersConnectedToHUD, ply) end) hook.add('ClientInitialized', 'vehicle_clientinitialized', function(ply) table.insert(self.initializedPlayers, ply) if self.initialized then net.start('VEHICLE_READY') net.send(ply, true) end end) else --@include ./hud.txt require('./hud.txt') net.receive('VEHICLE_READY', function() self:start() end) end end ---@return nil function Vehicle:start() self:createComponents() self:linkComponents() if SERVER then self:createIO() end self.initialized = true if SERVER then net.start('VEHICLE_READY') net.send(self.initializedPlayers, true) end end ---@param config any ---@return boolean function Vehicle:validateConfig(config) return type(config) == 'table' end ---@param name string ---@return PowertrainComponent function Vehicle:getComponentByName(name) return table.find(self.components, function(component) return component.name == name end) end -- ---@param type string -- ---@return PowertrainComponent[] -- function Vehicle:getComponentsByType(type) -- return table.find(self.components, function(component) -- return component.name == name -- end) -- end ---@return nil function Vehicle:createComponents() for _, componentConfig in pairs(self.config) do local component = PowertrainComponentFactory:create(self, componentConfig.Type, componentConfig.Name, componentConfig.Config) table.insert(self.components, component) end end ---@return nil function Vehicle:linkComponents() for _, componentConfig in pairs(self.config) do local component = self:getComponentByName(componentConfig.Name) if componentConfig.Input == nil then table.insert(self.headComponents, component) else local inputComponent = self:getComponentByName(componentConfig.Input) if inputComponent ~= nil then inputComponent:linkComponent(component) end end end if SERVER then print(Color(0, 255, 0), 'Powertrain tree:') for _, component in pairs(self.headComponents) do PrintTree(component) print(' ') end end end ---@return nil function Vehicle:createIO() local inputs = { Base = 'entity', Steer = 'number', Brake = 'number', Handbrake = 'number', } local outputs = {} for _, component in ipairs(self.components) do inputs = table.merge(inputs, component.wireInputs) outputs = table.merge(outputs, component.wireOutputs) end wire.adjustPorts(inputs, outputs) for _, component in ipairs(self.components) do component:onWirePortsReady() end end ---@return nil function Vehicle:handleWireInput(name, value) if name == 'Base' then self.base = value self.basePhysObject = isValid(value) and value:getPhysicsObject() or nil elseif name == 'Steer' then self.steer = value elseif name == 'Brake' then self.brake = value elseif name == 'Handbrake' then self.handbrake = value end if not self.initialized and self.base ~= nil and self.basePhysObject ~= nil then self:start() end end ---@return nil function Vehicle:update() if SERVER then local base = wire.ports.Base for _, component in pairs(self.headComponents) do component:forwardStep(0, 0) end for _, component in pairs(self.components) do component:updateWireOutputs() end -- net.start("CAR_SPEED") -- net.writeUInt(base:getVelocity():getLength()* 1.905 / 100000 * 3600, 12) -- net.send(self.playersConnectedToHUD, true) end --if isValid(base) and (self.clutch:getPress() == 1 or self.gearbox.ratio == 0) then -- local tiltForce = self.engine.torque * (-1 + self.engine.masterThrottle * 2) -- -- base:applyForceOffset(base:getUp() * tiltForce, base:getMassCenter() + base:getRight() * UNITS_PER_METER) -- base:applyForceOffset(-base:getUp() * tiltForce, base:getMassCenter() - base:getRight() * UNITS_PER_METER) --end end ---@return nil function PrintTree(root) if root == nil then return end local lines = { { Color(255, 255, 255), root.name } } local subtreeLines = PrintSubtree(root, '') for _, line in ipairs(subtreeLines) do table.insert(lines, line) end for _, line in ipairs(lines) do print(unpack(line)) end end ---@return (string | Color)[] function PrintSubtree(root, prefix) if root == nil then return {} end ---@type (string | Color)[] local lines = {} local left = root.outputB local right = root.output or root.outputA local hasLeft = left ~= nil local hasRight = right ~= nil if not hasLeft and not hasRight then return lines end if hasRight then local printStrand = hasLeft and (right.output ~= nil or right.outputA ~= nil or right.outputB ~= nil) local newPrefix = prefix .. (printStrand and '│ \t' or '\t\t') table.insert( lines, { Color(80, 80, 80), prefix .. (hasLeft and '├── ' or '└── '), Color(255, 255, 255), tostring(right.name) } ) local rightLines = PrintSubtree(right, newPrefix) for _, line in ipairs(rightLines) do table.insert(lines, line) end end if hasLeft then table.insert(lines, { Color(80, 80, 80), (hasRight and prefix or '') .. '└── ', Color(255, 255, 255), tostring(left.name) }) local leftLines = PrintSubtree(left, prefix .. '\t\t') for _, line in ipairs(leftLines) do table.insert(lines, line) end end return lines end return { Vehicle, POWERTRAIN_COMPONENT, }