From 8dc9689601379d779c8b43fd92f9a6b62739dc3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=B2=D0=B0=D0=BD=20=D0=93=D1=80=D0=B0=D1=87=D1=91?= =?UTF-8?q?=D0=B2?= Date: Sun, 19 Sep 2021 17:31:20 +0500 Subject: [PATCH 1/3] Implemented new input system --- koptilnya/input_system/cl_input.txt | 68 +++++++++++++++ koptilnya/input_system/main.txt | 29 ++++++ koptilnya/input_system/sv_input.txt | 131 ++++++++++++++++++++++++++++ 3 files changed, 228 insertions(+) create mode 100644 koptilnya/input_system/cl_input.txt create mode 100644 koptilnya/input_system/main.txt create mode 100644 koptilnya/input_system/sv_input.txt diff --git a/koptilnya/input_system/cl_input.txt b/koptilnya/input_system/cl_input.txt new file mode 100644 index 0000000..ef00bfa --- /dev/null +++ b/koptilnya/input_system/cl_input.txt @@ -0,0 +1,68 @@ +-- @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, 'press') + end + end) + + hook.add('inputReleased', 'KeyRelease', function(key) + if self.driver == CURRENT_PLAYER then + self:_trySetTargetValue(key, 'release') + end + end) +end + +function Input:_trySetTargetValue(key, evt) + local triggeredKey = self.keys[key] + + if triggeredKey ~= nil then + local targetValue = evt == 'press' and triggeredKey.Value or 0 + + 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, + Value = -1 + } + + self.keys[v.Positive] = { + Axle = k, + Value = 1 + } + end +end + +-- Public here + +function Input:initialize(options) + options = options or {} + self.keys = {} + self.driver = NULL_ENTITY + + self:_setupHooks(); + self:_setupNetListeners(); + self:_mapKeys(options.Axles) +end + +function Input:update() + +end diff --git a/koptilnya/input_system/main.txt b/koptilnya/input_system/main.txt new file mode 100644 index 0000000..7806b50 --- /dev/null +++ b/koptilnya/input_system/main.txt @@ -0,0 +1,29 @@ +-- @name Input System +-- @author DarkSupah +-- @shared +-- @include ./cl_input.txt +-- @include ./sv_input.txt +local Axles = { + Horizontal = { + Negative = KEY.A, + Positive = KEY.D, + Gravity = 0.05, + Sensitivity = 0.1 + } +} + +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) diff --git a/koptilnya/input_system/sv_input.txt b/koptilnya/input_system/sv_input.txt new file mode 100644 index 0000000..8629dca --- /dev/null +++ b/koptilnya/input_system/sv_input.txt @@ -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 From d5bbad6f1d983aa46368dcd334b2a31b80184d7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=B2=D0=B0=D0=BD=20=D0=93=D1=80=D0=B0=D1=87=D1=91?= =?UTF-8?q?=D0=B2?= Date: Sun, 19 Sep 2021 19:51:10 +0500 Subject: [PATCH 2/3] Fixed target values bug --- koptilnya/grip_steering/main.txt | 91 +++++++++++++++++++++++++++++ koptilnya/grip_steering/slave.txt | 25 ++++++++ koptilnya/input_system/cl_input.txt | 28 ++++++--- 3 files changed, 136 insertions(+), 8 deletions(-) create mode 100644 koptilnya/grip_steering/main.txt create mode 100644 koptilnya/grip_steering/slave.txt diff --git a/koptilnya/grip_steering/main.txt b/koptilnya/grip_steering/main.txt new file mode 100644 index 0000000..df2b42e --- /dev/null +++ b/koptilnya/grip_steering/main.txt @@ -0,0 +1,91 @@ +-- @name Grip Steering +-- @author DarkSupah +-- @server +-- @include ./slave.txt +Steering = class('Steering') + +require('./slave.txt') + +local options = { + Camber = 0, + Caster = 85, + Ackermann = 10, + Lock = 50 +} + +function Steering:_adjustPorts() + local inputs = { + Axles = 'table', + Base = 'entity', + LeftSlave = 'entity', + RightSlave = 'entity' + } + + local outputs = { + SteerAngle = 'number', + TargetAngle = '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, + 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 +end + +function Steering:initialize(options) + options = options or {} + + self.slavesConfig = { + Camber = options.Camber, + Caster = options.Caster, + Ackermann = options.Ackermann + } + + self.lock = 50 + + self.targetAngle = 0 + self.angle = 0 + + self.slaves = {} + + self:_adjustPorts() + self:_createSlaves() +end + +function Steering:update() + local horizontal = wire.ports.Axles.Horizontal.Value + + self.targetAngle = horizontal * self.lock + self.angle = -horizontal * self.lock + + 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) diff --git a/koptilnya/grip_steering/slave.txt b/koptilnya/grip_steering/slave.txt new file mode 100644 index 0000000..a5e125d --- /dev/null +++ b/koptilnya/grip_steering/slave.txt @@ -0,0 +1,25 @@ +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 +end + +function Slave:rotate(ang) + if self.entity:isFrozen() == false then + self.entity:setFrozen(1) + end + + local angle = Angle(0, ang, 0) + local transformedAngle = self.base:localToWorldAngles(angle) + + self.entity:setAngles(transformedAngle) + -- rotate slave here +end diff --git a/koptilnya/input_system/cl_input.txt b/koptilnya/input_system/cl_input.txt index ef00bfa..3d48cd0 100644 --- a/koptilnya/input_system/cl_input.txt +++ b/koptilnya/input_system/cl_input.txt @@ -7,22 +7,35 @@ Input = class('Input') function Input:_setupHooks() hook.add('inputPressed', 'KeyPress', function(key) if self.driver == CURRENT_PLAYER then - self:_trySetTargetValue(key, 'press') + self:_trySetTargetValue(key) end end) hook.add('inputReleased', 'KeyRelease', function(key) if self.driver == CURRENT_PLAYER then - self:_trySetTargetValue(key, 'release') + self:_trySetTargetValue(key) end end) end -function Input:_trySetTargetValue(key, evt) +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 targetValue = evt == 'press' and triggeredKey.Value or 0 + local triggeredAxle = self.axles[triggeredKey.Axle] + local targetValue = self:getAxleValue(triggeredAxle) net.start('SetTargetAxle') net.writeString(triggeredKey.Axle) @@ -40,13 +53,11 @@ end function Input:_mapKeys(axles) for k, v in pairs(axles) do self.keys[v.Negative] = { - Axle = k, - Value = -1 + Axle = k } self.keys[v.Positive] = { - Axle = k, - Value = 1 + Axle = k } end end @@ -55,6 +66,7 @@ end function Input:initialize(options) options = options or {} + self.axles = options.Axles self.keys = {} self.driver = NULL_ENTITY From f5192ebfffb4a86e29314496ca1c09036d162add Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=B2=D0=B0=D0=BD=20=D0=93=D1=80=D0=B0=D1=87=D1=91?= =?UTF-8?q?=D0=B2?= Date: Sun, 19 Sep 2021 23:38:05 +0500 Subject: [PATCH 3/3] Finished grip steering --- koptilnya/grip_steering/main.txt | 113 +++++++++++++++++++++++++++--- koptilnya/grip_steering/slave.txt | 8 ++- koptilnya/input_system/main.txt | 8 +-- 3 files changed, 115 insertions(+), 14 deletions(-) diff --git a/koptilnya/grip_steering/main.txt b/koptilnya/grip_steering/main.txt index df2b42e..e2bddf2 100644 --- a/koptilnya/grip_steering/main.txt +++ b/koptilnya/grip_steering/main.txt @@ -6,11 +6,40 @@ 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 = 0, - Caster = 85, - Ackermann = 10, - Lock = 50 + 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() @@ -23,7 +52,8 @@ function Steering:_adjustPorts() local outputs = { SteerAngle = 'number', - TargetAngle = 'number' + TargetAngle = 'number', + CorrectionAngle = 'number' } wire.adjustPorts(inputs, outputs) @@ -37,6 +67,7 @@ function Steering:_createSlaves() local slave = Slave:new({ Entity = v, IsLeft = k % 2 == 1, + Offset = 180, Base = wire.ports.Base, Camber = self.slavesConfig.Camber, Caster = self.slavesConfig.Caster, @@ -49,33 +80,99 @@ 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 = 50 + 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:_adjustPorts() 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 - self.angle = -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) diff --git a/koptilnya/grip_steering/slave.txt b/koptilnya/grip_steering/slave.txt index a5e125d..6497fff 100644 --- a/koptilnya/grip_steering/slave.txt +++ b/koptilnya/grip_steering/slave.txt @@ -10,6 +10,7 @@ function Slave:initialize(options) self.caster = options.Caster self.ackermann = options.Ackermann self.isLeft = options.IsLeft + self.offset = options.Offset end function Slave:rotate(ang) @@ -17,9 +18,12 @@ function Slave:rotate(ang) self.entity:setFrozen(1) end - local angle = Angle(0, ang, 0) + 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) - -- rotate slave here end diff --git a/koptilnya/input_system/main.txt b/koptilnya/input_system/main.txt index 7806b50..6bd7350 100644 --- a/koptilnya/input_system/main.txt +++ b/koptilnya/input_system/main.txt @@ -5,10 +5,10 @@ -- @include ./sv_input.txt local Axles = { Horizontal = { - Negative = KEY.A, - Positive = KEY.D, - Gravity = 0.05, - Sensitivity = 0.1 + Negative = KEY.D, + Positive = KEY.A, + Gravity = 0.1, + Sensitivity = 0.2 } }