254 lines
6.8 KiB
Lua
254 lines
6.8 KiB
Lua
--@include ./powertrain_component.txt
|
|
--@include ../wheel/wheel.txt
|
|
--@include /koptilnya/libs/constants.txt
|
|
--@include /koptilnya/libs/entity.txt
|
|
|
|
---@type PowertrainComponent
|
|
local PowertrainComponent = require('./powertrain_component.txt')
|
|
|
|
---@type CustomWheel
|
|
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 brakePower number
|
|
---@field handbrakePower number
|
|
---@field direction integer
|
|
---@field private rot number
|
|
---@field model string
|
|
---@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 + 0) * 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 + 0) * 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.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',
|
|
}
|
|
|
|
self.rot = 0
|
|
|
|
---@type Entity?
|
|
self.entity = nil
|
|
|
|
---@type Hologram?
|
|
self.holo = nil
|
|
|
|
self.customWheel = CustomWheel:new(
|
|
table.merge(
|
|
table.copy(config.CustomWheel),
|
|
{ Direction = self.direction, Name = self.name, ParentPhysObj = vehicle.basePhysObject }
|
|
)
|
|
)
|
|
|
|
hook.add('Input', 'vehicle_wheel_update' .. self.name, function(name, value)
|
|
if name == self.name then
|
|
self:setEntity(value)
|
|
end
|
|
end)
|
|
end
|
|
|
|
---@override
|
|
---@return nil
|
|
function Wheel:onWirePortsReady()
|
|
self:setEntity(self.CONFIG.Entity or wire.ports[self.name])
|
|
end
|
|
|
|
---@private
|
|
---@param entity Entity
|
|
---@return nil
|
|
function Wheel:setEntity(entity)
|
|
self.entity = entity
|
|
self.customWheel:setEntity(entity)
|
|
self.customWheel.radius = self:getEntityRadius()
|
|
|
|
if isValid(self.holo) then
|
|
self.holo:remove()
|
|
end
|
|
|
|
self.holo = self:createHolo(self.entity)
|
|
end
|
|
|
|
---@return number
|
|
function Wheel:getEntityRadius()
|
|
if not isValid(self.entity) then
|
|
return 0
|
|
end
|
|
|
|
return self.entity:getModelRadius() * UNITS_TO_METERS
|
|
end
|
|
|
|
---@return Hologram?
|
|
function Wheel:createHolo(entity)
|
|
if not isValid(entity) then
|
|
return nil
|
|
end
|
|
|
|
local holo = hologram.create(entity:getPos(), entity:getAngles(), self.model or '')
|
|
|
|
if holo == nil or not isValid(holo) then
|
|
return nil
|
|
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()
|
|
end
|
|
|
|
---@return number
|
|
function Wheel:queryInertia()
|
|
return self.customWheel.inertia
|
|
end
|
|
|
|
---@return number
|
|
function Wheel:queryAngularVelocity(angularVelocity)
|
|
self.customWheel.angularVelocity = angularVelocity
|
|
|
|
return self.customWheel.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
|
|
|
|
---@type fun(vehicle: KPTLVehicle, name?: string, config?: WheelConfig): Wheel
|
|
Wheel.new = Wheel.new
|
|
|
|
return Wheel
|