commit
546f3a562e
188
koptilnya/grip_steering/main.txt
Normal file
188
koptilnya/grip_steering/main.txt
Normal file
@ -0,0 +1,188 @@
|
||||
-- @name Grip Steering
|
||||
-- @author DarkSupah
|
||||
-- @server
|
||||
-- @include ./slave.txt
|
||||
Steering = class('Steering')
|
||||
|
||||
require('./slave.txt')
|
||||
|
||||
function inrange(val, min, max)
|
||||
return val >= min and val <= max
|
||||
end
|
||||
|
||||
function isNaN(val)
|
||||
return val ~= val
|
||||
end
|
||||
|
||||
function vectorIsNaN(vec)
|
||||
return isNaN(vec[1]) or isNaN(vec[2]) or isNaN(vec[3])
|
||||
end
|
||||
|
||||
function getLocalVelocity(ent)
|
||||
return ent:worldToLocal((ent:getVelocity() + ent:getPos()))
|
||||
end
|
||||
|
||||
local options = {
|
||||
Camber = -2,
|
||||
Caster = 5,
|
||||
Ackermann = 15,
|
||||
Lock = 45,
|
||||
|
||||
ZeroThreshold = 2,
|
||||
|
||||
StartSpeedThreshold = 40,
|
||||
EndSpeedThreshold = 150,
|
||||
SpeedCoeff = 0.2,
|
||||
|
||||
StartCorrectionThreshold = 5,
|
||||
EndCorrectionThreshold = 40,
|
||||
CorrectionOnCoeff = 0.6,
|
||||
CorrectionOffCoeff = 1,
|
||||
CorrectionOnLerp = 0.3,
|
||||
CorrectionOffLerp = 0.6
|
||||
}
|
||||
|
||||
function Steering:_adjustPorts()
|
||||
local inputs = {
|
||||
Axles = 'table',
|
||||
Base = 'entity',
|
||||
LeftSlave = 'entity',
|
||||
RightSlave = 'entity'
|
||||
}
|
||||
|
||||
local outputs = {
|
||||
SteerAngle = 'number',
|
||||
TargetAngle = 'number',
|
||||
CorrectionAngle = 'number'
|
||||
}
|
||||
|
||||
wire.adjustPorts(inputs, outputs)
|
||||
end
|
||||
|
||||
function Steering:_createSlaves()
|
||||
local entities = {wire.ports.LeftSlave, wire.ports.RightSlave}
|
||||
local slaves = {}
|
||||
|
||||
for k, v in pairs(entities) do
|
||||
local slave = Slave:new({
|
||||
Entity = v,
|
||||
IsLeft = k % 2 == 1,
|
||||
Offset = 180,
|
||||
Base = wire.ports.Base,
|
||||
Camber = self.slavesConfig.Camber,
|
||||
Caster = self.slavesConfig.Caster,
|
||||
Ackermann = self.slavesConfig.Ackermann
|
||||
})
|
||||
|
||||
table.insert(self.slaves, slave)
|
||||
end
|
||||
end
|
||||
|
||||
function Steering:_updateOutputs()
|
||||
wire.ports.TargetAngle = self.targetAngle
|
||||
wire.ports.SteerAngle = self.angle
|
||||
wire.ports.CorrectionAngle = self.correction
|
||||
end
|
||||
|
||||
function Steering:_getCorrectionGradient(speed)
|
||||
return math.clamp(math.remap(speed, self.startCorrectionThreshold, self.endCorrectionThreshold, 0, 1), 0, 1)
|
||||
end
|
||||
|
||||
function Steering:getBaseSpeed()
|
||||
local MPH = self.base:getVelocity():getLength() * 3600 / 63360
|
||||
local KPH = MPH * 1.609
|
||||
|
||||
return KPH
|
||||
end
|
||||
|
||||
function Steering:_getSpeedCorrectionGradient(speed)
|
||||
return math.clamp(math.remap(speed, self.startSpeedThreshold, self.endSpeedThreshold, 0, 1), 0, 1)
|
||||
end
|
||||
|
||||
function Steering:_getCorrectionAngle()
|
||||
local localVel = getLocalVelocity(self.base)
|
||||
local forwardVel = (localVel * Vector(1, 0, 0)):getNormalized()
|
||||
local planarVec = (localVel * Vector(1, 1, 0)):getNormalized()
|
||||
|
||||
forwardVel = vectorIsNaN(forwardVel) and Vector(0, 0, 0) or forwardVel
|
||||
planarVec = vectorIsNaN(planarVec) and Vector(0, 0, 0) or planarVec
|
||||
|
||||
local cross = forwardVel:cross(planarVec)
|
||||
|
||||
return math.deg(math.acos(forwardVel:dot(planarVec))) * math.sign(localVel[2])
|
||||
end
|
||||
|
||||
function Steering:initialize(options)
|
||||
self:_adjustPorts()
|
||||
|
||||
options = options or {}
|
||||
|
||||
self.base = wire.ports.Base
|
||||
|
||||
self.slavesConfig = {
|
||||
Camber = options.Camber,
|
||||
Caster = options.Caster,
|
||||
Ackermann = options.Ackermann
|
||||
}
|
||||
|
||||
self.lock = options.Lock
|
||||
|
||||
self.targetAngle = 0
|
||||
self.angle = 0
|
||||
|
||||
self.zeroThreshold = options.ZeroThreshold
|
||||
|
||||
self.speedCoeff = options.SpeedCoeff
|
||||
self.startSpeedThreshold = options.StartSpeedThreshold
|
||||
self.endSpeedThreshold = options.EndSpeedThreshold
|
||||
|
||||
self.startCorrectionThreshold = options.StartCorrectionThreshold
|
||||
self.endCorrectionThreshold = options.EndCorrectionThreshold
|
||||
self.correctionOnCoeff = options.CorrectionOnCoeff
|
||||
self.correctionOffCoeff = options.CorrectionOffCoeff
|
||||
|
||||
self.correctionOnLerp = options.CorrectionOnLerp
|
||||
self.correctionOffLerp = options.CorrectionOffLerp
|
||||
|
||||
self.correction = 0
|
||||
|
||||
self.slaves = {}
|
||||
|
||||
self:_createSlaves()
|
||||
end
|
||||
|
||||
function Steering:update()
|
||||
local horizontal = wire.ports.Axles.Horizontal.Value
|
||||
local horizontalTarget = wire.ports.Axles.Horizontal.Target
|
||||
|
||||
local baseSpeedKMH = self:getBaseSpeed()
|
||||
local angleCorrection = self:_getCorrectionGradient(baseSpeedKMH)
|
||||
local speedCorrection = self.angle * self:_getSpeedCorrectionGradient(baseSpeedKMH) * self.speedCoeff
|
||||
|
||||
self.targetAngle = horizontal * self.lock
|
||||
|
||||
local correctionCoeff = horizontalTarget ~= 0 and self.correctionOnCoeff or self.correctionOffCoeff
|
||||
local correctionLerpCoeff = horizontalTarget ~= 0 and self.correctionOnLerp or self.correctionOffLerp
|
||||
local correction = self:_getCorrectionAngle() * angleCorrection * correctionCoeff
|
||||
self.correction = math.lerp(correctionLerpCoeff, self.correction, isNaN(correction) and 0 or correction)
|
||||
|
||||
local possibleAngle = math.clamp(horizontal * self.lock + speedCorrection + self.correction, -self.lock, self.lock)
|
||||
|
||||
if inrange(possibleAngle, -self.zeroThreshold, self.zeroThreshold) then
|
||||
self.angle = 0
|
||||
else
|
||||
self.angle = possibleAngle
|
||||
end
|
||||
|
||||
for k, v in pairs(self.slaves) do
|
||||
v:rotate(self.angle)
|
||||
end
|
||||
|
||||
self:_updateOutputs()
|
||||
end
|
||||
|
||||
local steering = Steering:new(options)
|
||||
|
||||
hook.add('think', 'SteeringUpdate', function()
|
||||
steering:update()
|
||||
end)
|
||||
29
koptilnya/grip_steering/slave.txt
Normal file
29
koptilnya/grip_steering/slave.txt
Normal file
@ -0,0 +1,29 @@
|
||||
Slave = class('Slave')
|
||||
|
||||
function Slave:initialize(options)
|
||||
options = options or {}
|
||||
|
||||
self.entity = options.Entity
|
||||
self.base = options.Base
|
||||
|
||||
self.camber = options.Camber
|
||||
self.caster = options.Caster
|
||||
self.ackermann = options.Ackermann
|
||||
self.isLeft = options.IsLeft
|
||||
self.offset = options.Offset
|
||||
end
|
||||
|
||||
function Slave:rotate(ang)
|
||||
if self.entity:isFrozen() == false then
|
||||
self.entity:setFrozen(1)
|
||||
end
|
||||
|
||||
local ackermann = math.sin(math.rad(ang)) ^ 2 * self.ackermann * (self.isLeft and -1 or 1)
|
||||
local yaw = self.offset + ang + ackermann
|
||||
local roll = (self.isLeft and 1 or -1) * self.camber - ang / 270 * self.caster
|
||||
|
||||
local angle = Angle(0, yaw, roll)
|
||||
local transformedAngle = self.base:localToWorldAngles(angle)
|
||||
|
||||
self.entity:setAngles(transformedAngle)
|
||||
end
|
||||
80
koptilnya/input_system/cl_input.txt
Normal file
80
koptilnya/input_system/cl_input.txt
Normal file
@ -0,0 +1,80 @@
|
||||
-- @include ../libs/constants.txt
|
||||
require('../libs/constants.txt')
|
||||
|
||||
Input = class('Input')
|
||||
-- Private here
|
||||
|
||||
function Input:_setupHooks()
|
||||
hook.add('inputPressed', 'KeyPress', function(key)
|
||||
if self.driver == CURRENT_PLAYER then
|
||||
self:_trySetTargetValue(key)
|
||||
end
|
||||
end)
|
||||
|
||||
hook.add('inputReleased', 'KeyRelease', function(key)
|
||||
if self.driver == CURRENT_PLAYER then
|
||||
self:_trySetTargetValue(key)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function Input:bothKeysHolding(axle)
|
||||
return (input.isKeyDown(axle.Positive) == true) and (input.isKeyDown(axle.Negative) == true)
|
||||
end
|
||||
|
||||
function Input:noKeysHolding(axle)
|
||||
return (input.isKeyDown(axle.Positive) == false) and (input.isKeyDown(axle.Negative) == false)
|
||||
end
|
||||
|
||||
function Input:getAxleValue(axle)
|
||||
return (input.isKeyDown(axle.Positive) and 1 or 0) - (input.isKeyDown(axle.Negative) and 1 or 0)
|
||||
end
|
||||
|
||||
function Input:_trySetTargetValue(key)
|
||||
local triggeredKey = self.keys[key]
|
||||
|
||||
if triggeredKey ~= nil then
|
||||
local triggeredAxle = self.axles[triggeredKey.Axle]
|
||||
local targetValue = self:getAxleValue(triggeredAxle)
|
||||
|
||||
net.start('SetTargetAxle')
|
||||
net.writeString(triggeredKey.Axle)
|
||||
net.writeInt(targetValue, 4)
|
||||
net.send()
|
||||
end
|
||||
end
|
||||
|
||||
function Input:_setupNetListeners()
|
||||
net.receive('SyncDriver', function()
|
||||
self.driver = net.readEntity()
|
||||
end)
|
||||
end
|
||||
|
||||
function Input:_mapKeys(axles)
|
||||
for k, v in pairs(axles) do
|
||||
self.keys[v.Negative] = {
|
||||
Axle = k
|
||||
}
|
||||
|
||||
self.keys[v.Positive] = {
|
||||
Axle = k
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
-- Public here
|
||||
|
||||
function Input:initialize(options)
|
||||
options = options or {}
|
||||
self.axles = options.Axles
|
||||
self.keys = {}
|
||||
self.driver = NULL_ENTITY
|
||||
|
||||
self:_setupHooks();
|
||||
self:_setupNetListeners();
|
||||
self:_mapKeys(options.Axles)
|
||||
end
|
||||
|
||||
function Input:update()
|
||||
|
||||
end
|
||||
29
koptilnya/input_system/main.txt
Normal file
29
koptilnya/input_system/main.txt
Normal file
@ -0,0 +1,29 @@
|
||||
-- @name Input System
|
||||
-- @author DarkSupah
|
||||
-- @shared
|
||||
-- @include ./cl_input.txt
|
||||
-- @include ./sv_input.txt
|
||||
local Axles = {
|
||||
Horizontal = {
|
||||
Negative = KEY.D,
|
||||
Positive = KEY.A,
|
||||
Gravity = 0.1,
|
||||
Sensitivity = 0.2
|
||||
}
|
||||
}
|
||||
|
||||
local options = {
|
||||
Axles = Axles
|
||||
}
|
||||
|
||||
if SERVER then
|
||||
require('./sv_input.txt')
|
||||
else
|
||||
require('./cl_input.txt')
|
||||
end
|
||||
|
||||
local input = Input:new(options)
|
||||
|
||||
hook.add('tick', 'InputUpdate', function()
|
||||
input:update()
|
||||
end)
|
||||
131
koptilnya/input_system/sv_input.txt
Normal file
131
koptilnya/input_system/sv_input.txt
Normal file
@ -0,0 +1,131 @@
|
||||
-- @include ../libs/constants.txt
|
||||
require('../libs/constants.txt')
|
||||
|
||||
Input = class('Input')
|
||||
|
||||
-- Private here
|
||||
|
||||
function Input:_adjustPorts()
|
||||
local inputs = {
|
||||
Seat = 'entity'
|
||||
}
|
||||
local outputs = {
|
||||
Driver = 'entity',
|
||||
Axles = 'table'
|
||||
}
|
||||
|
||||
wire.adjustPorts(inputs, outputs)
|
||||
end
|
||||
|
||||
function Input:_syncDriver(ply)
|
||||
net.start('SyncDriver')
|
||||
net.writeEntity(ply)
|
||||
net.send()
|
||||
end
|
||||
|
||||
function Input:_setupHooks()
|
||||
hook.add('PlayerEnteredVehicle', 'VehicleEnter', function(ply, vehicle)
|
||||
if vehicle == self.seat then
|
||||
self.driver = ply
|
||||
self:_syncDriver(self.driver)
|
||||
end
|
||||
|
||||
end)
|
||||
|
||||
hook.add('PlayerLeaveVehicle', 'VehicleLeave', function(_, vehicle)
|
||||
if vehicle == self.seat then
|
||||
self.driver = NULL_ENTITY
|
||||
self:_syncDriver(self.driver)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function Input:_setTargetValue(target)
|
||||
local targetAxle = self.axles[target.Axle]
|
||||
targetAxle.Target = target.Target
|
||||
end
|
||||
|
||||
function Input:_setupNetListeners()
|
||||
net.receive('SetTargetAxle', function()
|
||||
local target = {
|
||||
Axle = net.readString(),
|
||||
Target = net.readInt(4)
|
||||
}
|
||||
|
||||
self:_setTargetValue(target)
|
||||
end)
|
||||
end
|
||||
|
||||
function Input:_updateOutputs()
|
||||
local driver = self.driver
|
||||
|
||||
if self.driver:isValid() == false then
|
||||
driver = NULL_ENTITY
|
||||
end
|
||||
|
||||
wire.ports.Driver = driver
|
||||
wire.ports.Axles = self.axles
|
||||
end
|
||||
|
||||
function Input:_setupAxles(axles)
|
||||
for k, v in pairs(axles) do
|
||||
self.axles[k] = {
|
||||
Value = 0,
|
||||
Target = 0,
|
||||
Gravity = v.Gravity,
|
||||
Sensitivity = v.Sensitivity
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
function Input:_getAxleValue(axle)
|
||||
local usedLerpCoeff = axle.Target ~= 0 and axle.Sensitivity or axle.Gravity
|
||||
|
||||
return math.lerp(usedLerpCoeff, axle.Value, axle.Target)
|
||||
end
|
||||
|
||||
function Input:_updateAxles()
|
||||
for k, v in pairs(self.axles) do
|
||||
v.Value = self:_getAxleValue(v)
|
||||
end
|
||||
end
|
||||
|
||||
-- Public here
|
||||
|
||||
function Input:initialize(options)
|
||||
options = options or {}
|
||||
local axles = options.Axles or {}
|
||||
|
||||
self.axles = {}
|
||||
|
||||
self:_adjustPorts()
|
||||
self:_setupHooks()
|
||||
self:_setupNetListeners()
|
||||
|
||||
self:_setupAxles(axles)
|
||||
|
||||
self.seat = wire.ports.Seat
|
||||
self.driver = self:getDriver()
|
||||
|
||||
-- in case chip was reset
|
||||
self:_syncDriver(self.driver)
|
||||
end
|
||||
|
||||
function Input:getDriver()
|
||||
if self.seat == nil or self.seat:isValid() == false then
|
||||
return NULL_ENTITY
|
||||
end
|
||||
|
||||
local driver = self.seat:getDriver()
|
||||
|
||||
if driver:isValid() == true then
|
||||
return driver
|
||||
else
|
||||
return NULL_ENTITY
|
||||
end
|
||||
end
|
||||
|
||||
function Input:update()
|
||||
self:_updateOutputs()
|
||||
self:_updateAxles()
|
||||
end
|
||||
Loading…
x
Reference in New Issue
Block a user