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

232 lines
6.6 KiB
Lua

--@include ./powertrain_component.txt
--@include ../wheel/wheel.txt
--@include /koptilnya/libs/constants.txt
--@include /koptilnya/libs/entity.txt
local PowertrainComponent = require('./powertrain_component.txt')
local CustomWheel = require('../wheel/wheel.txt')
require('/koptilnya/libs/constants.txt')
require('/koptilnya/libs/entity.txt')
---@class WheelConfig: PowertrainComponentConfig
---@field Entity? Entity
---@field BrakePower? number
---@field HandbrakePower? number
---@field Model? string
---@field Offset? number In degrees
---@field CustomWheel? CustomWheelConfig
---@class Wheel: PowertrainComponent
---@field entity Entity
---@field brakePower number
---@field handbrakePower number
---@field direction integer
---@field private rot number
---@field model string
---@field holo Hologram | Entity
---@field customWheel CustomWheel
local Wheel = class('Wheel', PowertrainComponent)
---@private
---@param vehicle KPTLVehicle
---@param name string
---@param config WheelConfig
function Wheel:initialize(vehicle, name, config)
PowertrainComponent.initialize(self, vehicle, name, config)
if CLIENT and self.DEBUG then
local mat = render.createMaterial('models/debug/debugwhite')
local lerpLoad = 0
local lerpForwardFrictionForce = 0
local lerpSideFrictionForce = 0
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:localToWorld(Vector(0, 0, -self.DEBUG_DATA.radius * UNITS_PER_METER))
render.setMaterial(mat)
render.setColor(Color(255, 0, 0, 200))
lerpForwardFrictionForce = math.lerp(0.1, lerpForwardFrictionForce, self.DEBUG_DATA.forwardFrictionForce)
render.draw3DBeam(pos, pos + ((lerpForwardFrictionForce / 500 + 16) * self.DEBUG_DATA.forward), 1, 0, 0)
render.setColor(Color(0, 255, 0, 255))
lerpSideFrictionForce = math.lerp(0.1, lerpSideFrictionForce, self.DEBUG_DATA.sideFrictionForce)
render.draw3DBeam(pos, pos + ((lerpSideFrictionForce / 500 + 16) * self.DEBUG_DATA.right), 1, 0, 0)
render.setColor(Color(0, 0, 255, 200))
lerpLoad = math.lerp(0.1, lerpLoad, self.DEBUG_DATA.load)
render.draw3DBeam(pos, pos + ((lerpLoad / 1000 * 4 + 16) * self.DEBUG_DATA.up), 1, 0, 0)
end)
if player() == owner() and not render.isHUDActive() then
enableHud(owner(), true)
end
return
end
self.entity = config.Entity or NULL_ENTITY
self.brakePower = config.BrakePower or 0
self.handbrakePower = config.HandbrakePower or 0
self.model = config.Model or ''
self.direction = math.sign(math.cos(math.rad(config.Offset or 0)))
self.wireInputs = {
[self.name] = 'entity',
}
self.wireOutputs = {
[string.format('%s_RPM', self.name)] = 'number',
[string.format('%s_Fx', self.name)] = 'number',
[string.format('%s_CTq', self.name)] = 'number',
}
self.rot = 0
self.holo = self:createHolo(self.entity)
self.customWheel = CustomWheel:new(table.merge(table.copy(config.CustomWheel), { Direction = self.direction, Name = self.name }))
hook.add('Input', 'vehicle_wheel_update' .. self.name, function(name, value)
if name == self.name then
self.entity = value
self.customWheel:setEntity(value)
if isValid(self.holo) then
self.holo:remove()
end
self.holo = self:createHolo(self.entity)
self.customWheel.radius = self:getEntityRadius()
end
end)
self.steerVelocity = 0
end
function Wheel:getEntityRadius()
if not isValid(self.entity) then
return 0
end
return self.entity:getModelRadius() * UNITS_TO_METERS
end
---@return Hologram | Entity
function Wheel:createHolo(entity)
if not isValid(entity) then
return NULL_ENTITY
end
local holo = hologram.create(entity:getPos(), entity:getAngles(), self.model or '')
if holo == nil or isValid(holo) then
return NULL_ENTITY
end
holo:setParent(entity)
entity:setColor(Color(0, 0, 0, 0))
return holo
end
---@return nil
function Wheel:updateWireOutputs()
PowertrainComponent.updateWireOutputs(self)
wire.ports[string.format('%s_RPM', self.name)] = self.customWheel:getRPM()
wire.ports[string.format('%s_Fx', self.name)] = self.customWheel.forwardFriction.force
wire.ports[string.format('%s_CTq', self.name)] = self.customWheel.counterTorque
end
---@return number
function Wheel:queryInertia()
return self.customWheel.inertia
end
---@return number
function Wheel:queryAngularVelocity()
return self.angularVelocity
end
---@param torque number
---@param inertia number
---@return number
function Wheel:forwardStep(torque, inertia)
if not isValid(self.customWheel) then
return 0
end
self.customWheel.motorTorque = torque
self.customWheel.brakeTorque = math.max(self.brakePower * self.vehicle.brake, self.handbrakePower * self.vehicle.handbrake)
self.customWheel:update()
self.angularVelocity = self.customWheel.angularVelocity
if self.customWheel.hasHit and isValid(self.vehicle.basePhysObject) then
local surfaceForceVector = self.customWheel.right * self.customWheel.sideFriction.force
+ self.customWheel.forward * self.customWheel.forwardFriction.force
self.vehicle.basePhysObject:applyForceOffset(
surfaceForceVector,
self.entity:localToWorld(Vector(0, 0, -self.customWheel.radius * UNITS_PER_METER))
)
end
if isValid(self.holo) then
-- Step 1: Update rotational state
self.rot = self.rot + self.angularVelocity * TICK_INTERVAL * self.direction
self.rot = self.rot % (2 * math.pi)
-- Step 2: Apply spin (rolling) to forward and up directions
local spunFwd = self.customWheel.forward:rotateAroundAxis(self.customWheel.right, nil, self.rot)
local spunUp = self.customWheel.up:rotateAroundAxis(self.customWheel.right, nil, self.rot)
-- Step 3: Get quaternion from spun directions
local visualQuat = spunFwd:getQuaternion(spunUp)
-- Step 4: Apply orientation to holo
self.holo:setAngles(visualQuat:getEulerAngle())
end
return self.customWheel.counterTorque
end
---@return nil
function Wheel:serializeDebugData()
net.writeEntity(self.entity)
net.writeFloat(self.customWheel.radius)
net.writeVector(self.customWheel.forward)
net.writeVector(self.customWheel.right)
net.writeVector(self.customWheel.up)
net.writeFloat(self.customWheel.load)
net.writeFloat(self.customWheel.forwardFriction.force)
net.writeFloat(self.customWheel.sideFriction.force)
end
---@param result table
---@return nil
function Wheel:deserializeDebugData(result)
net.readEntity(function(ent)
result.entity = ent
end)
result.radius = net.readFloat()
result.forward = net.readVector()
result.right = net.readVector()
result.up = net.readVector()
result.load = net.readFloat()
result.forwardFrictionForce = net.readFloat()
result.sideFrictionForce = net.readFloat()
end
return Wheel