diff --git a/koptilnya/engine_rework/constants.txt b/koptilnya/engine_rework/constants.txt new file mode 100644 index 0000000..ab4dd5b --- /dev/null +++ b/koptilnya/engine_rework/constants.txt @@ -0,0 +1,3 @@ +NULL_ENTITY = entity(0) +CURRENT_PLAYER = player() +OWNER = owner() diff --git a/koptilnya/engine_rework/controller.txt b/koptilnya/engine_rework/controller.txt new file mode 100644 index 0000000..927b716 --- /dev/null +++ b/koptilnya/engine_rework/controller.txt @@ -0,0 +1,132 @@ +-- @name Engine +-- @author DarkSupah, Opti1337, .hemp +-- @shared +-- @include ./constants.txt +-- @include ./engine.txt +-- @include ./gearbox.txt +-- @include ./input.txt +require("./constants.txt") +require("./engine.txt") +require("./gearbox.txt") +require("./input.txt") + +local configPath = "engine/" + +local inputConfigPath = "input.json" +local engineConfigPath = "engine.json" +local gearboxConfigPath = "gearbox.json" + +Controller = class("Controller") + +function Controller:initialize(inputController, engine, gearbox, axles) + self.inputController = inputController + self.engine = engine + self.gearbox = gearbox + self.axles = axles + + if SERVER then + hook.add("PlayerEnteredVehicle", "enterHandler", function(ply, vehicle) + if (vehicle == wire.ports.Seat) then + self.inputController:setPlayer(ply) + hook.run("DriverChanged", ply) + end + end) + + hook.add("PlayerLeaveVehicle", "exitHandler", function(ply, vehicle) + if (vehicle == wire.ports.Seat) then + self.inputController:setPlayer(NULL_ENTITY) + hook.run("DriverChanged", NULL_ENTITY) + end + end) + + net.receive("serverConfigs", function() + local inputConfig = net.readTable() + local engineConfig = net.readTable() + local gearboxConfig = net.readTable() + + self.inputController:setConfig(inputConfig) + self.engine:setConfig(engineConfig) + self.gearbox:setConfig(gearboxConfig) + + net.start("clientConfigs") + net.writeTable(inputConfig) + net.writeTable(engineConfig) + net.writeTable(gearboxConfig) + net.send() + end) + end + + if CLIENT then + net.receive("clientConfigs", function() + self.inputController:setConfig(net.readTable()) + self.engine:setConfig(net.readTable()) + self.gearbox:setConfig(net.readTable()) + end) + end + +end + +function Controller:update() + self.inputController:update() + self.engine:update() + self.gearbox:update() + + self.engine:setThrottle(self.inputController.throttle.value) +end + +local inputController = InputController:new() + +local engine = Engine:new() +local gearbox = Gearbox:new() + +local controller = Controller:new(inputController, engine, gearbox) + +if SERVER then + wire.adjustPorts({ + Seat = "entity" + }, { + Input = "table", + Engine = "table" + }) + + if wire.ports.Seat:isValid() then + local possibleDriver = wire.ports.Seat:getDriver() + + if possibleDriver:isValid() then + controller.inputController:setPlayer(possibleDriver) + wire.ports.Driver = possibleDriver + end + end + + hook.add("DriverChanged", "driverChangeHandler", function(ply) + wire.ports.Driver = ply + end) + + hook.add("tick", "update", function() + controller:update() + + wire.ports.Engine = controller.engine + wire.ports.Input = controller.inputController + + end) + +end + +if CLIENT then + if CURRENT_PLAYER == OWNER then + local inputConfigFile = file.read(configPath .. inputConfigPath) + local inputConfig = json.decode(inputConfigFile) + + local engineConfigFile = file.read(configPath .. engineConfigPath) + local engineConfig = json.decode(engineConfigFile) + + local gearboxConfigFile = file.read(configPath .. gearboxConfigPath) + local gearboxConfig = json.decode(gearboxConfigFile) + + net.start("serverConfigs") + net.writeTable(inputConfig) + net.writeTable(engineConfig) + net.writeTable(gearboxConfig) + net.send() + end +end diff --git a/koptilnya/engine_rework/engine.txt b/koptilnya/engine_rework/engine.txt new file mode 100644 index 0000000..686de7c --- /dev/null +++ b/koptilnya/engine_rework/engine.txt @@ -0,0 +1,121 @@ +-- @include ./math.txt +require("./math.txt") + +Engine = class("Engine") + +function Engine:initialize() + self.config = {} + + if SERVER then + self.active = true + + self.desiredRPM = 0 + self.RPM = 0 + + self.throttle = 0 + + self.flywheelInertiaMoment = 0 + self.volume = 0 + self.peakTq = 0 + self.torque = 0 + self.power = 0 + + self.gearboxRPM = 0 + + self.torqueTable = {} + end +end + +if SERVER then + + function Engine:setThrottle(throttle) + self.throttle = throttle + end + + function Engine:update() + -- check if config table is not empty + if next(self.config) ~= nil then + local activeCoeff = self.active and 1 or 0 + + self.desiredRPM = self.throttle * self.config.maxRPM + self.config.idleRPM + + local masterThrottle = activeCoeff * self.RPM > self.config.maxRPM and 0 or 1 + + self.RPM = math.lerp(1 / (self.flywheelInertiaMoment * 100), self.RPM, self.desiredRPM * masterThrottle) + + self:calculateTorque() + end + + end + + function Engine:updateStats() + local bore = self.config.bore + local stroke = self.config.stroke + local flywheelMass = self.config.flywheelMass + local cylinders = self.config.cylinders + local BMEP = self.config.BMEP + + self.flywheelInertiaMoment = (stroke / 1000) * flywheelMass + + self.volume = math.floor((bore / 2) ^ 2 * stroke * cylinders * math.pi / 1000) + + self.peakTq = (stroke / bore) * self.volume * BMEP / 100 + + self.torqueTable = {} + + for i = 1, #self.config.curve do + table.insert(self.torqueTable, self:getTorqueValue(i / #self.config.curve)) + end + + print("Peak Torque: " .. math.ceil(self.peakTq) .. "nm") + end + + function Engine:calculateTorque() + local RPMfrac = math.min(1, self.RPM / self.config.maxRPM) + local tableCount = table.count(self.torqueTable) + + local lerpTo = math.ceil(RPMfrac * tableCount) + local lerpFrom = math.floor(RPMfrac * tableCount) + + local lerpToVal = self.torqueTable[lerpTo] or 0 + local lerpFromVal = self.torqueTable[lerpFrom] or 0 + + local frac = (RPMfrac * tableCount) % 1 + local minActiveThrottle = self.config.idleRPM / self.config.maxRPM + + local value = math.max(minActiveThrottle, self.throttle) * math.lerp(frac, lerpFromVal, lerpToVal) * self.peakTq + + local overdrive = math.clamp(1 - self.gearboxRPM / self.desiredRPM, -1, 0) + + local idleRPM = self.config.idleRPM + local maxRPM = self.config.maxRPM + local brakeTorqueCoeff = self.config.brakeTorqueCoeff + + local brakeTorque = -value * math.max(1 - self.throttle, idleRPM / maxRPM, -overdrive) * + (self.gearboxRPM / maxRPM) * brakeTorqueCoeff + + local RPMLimiter = math.clamp(1 - self.throttle * (self.gearboxRPM / self.desiredRPM), self.throttle, 1) + local netTq = value * math.max(self.throttle, idleRPM / maxRPM) * RPMLimiter + brakeTorque + + self.torque = netTq + end + + function Engine:getTorqueValue(num) + local torque = 0 + + for key, segment in pairs(self.config.curve) do + torque = torque + segment * polinom(key, table.count(self.config.curve), num) + end + + return torque + end + +end + +function Engine:setConfig(config) + self.config = config + + if SERVER then + self:updateStats() + end +end diff --git a/koptilnya/engine_rework/gearbox.txt b/koptilnya/engine_rework/gearbox.txt new file mode 100644 index 0000000..430644b --- /dev/null +++ b/koptilnya/engine_rework/gearbox.txt @@ -0,0 +1,13 @@ +Gearbox = class("Gearbox") + +function Gearbox:initialize() + self.config = {} +end + +function Gearbox:update() + +end + +function Gearbox:setConfig(config) + self.config = config +end diff --git a/koptilnya/engine_rework/input.txt b/koptilnya/engine_rework/input.txt new file mode 100644 index 0000000..36deb29 --- /dev/null +++ b/koptilnya/engine_rework/input.txt @@ -0,0 +1,104 @@ +-- @include ./constants.txt +require("./constants.txt") + +InputController = class("InputController") + +function InputController:initialize() + self.ply = NULL_ENTITY + + self.config = {} -- keys and pedal speed config + + if SERVER then + self.throttle = { + target = 0, + value = 0 + } + self.brake = { + target = 0, + value = 0 + } + self.clutch = { + target = 0, + value = 0 + } + self.start = { + target = 0, + value = 0, + type = "keyPress" + } + + net.receive("input", function() + local inputName = net.readString() + local target = net.readBool() + + if self[inputName] then + self[inputName].target = target and (self.config[inputName] and self.config[inputName].max or 1) or 0 + end + end) + end + + if CLIENT then + function setTargetInput(pressedKey, target) + local key = string.upper(input.getKeyName(pressedKey)) + + if self.keyToInputName[key] then + net.start("input") + net.writeString(self.keyToInputName[key].inputName) + net.writeBool(target) + net.send() + end + end + + hook.add("inputPressed", "inputPress", function(key) + if (self.ply ~= NULL_ENTITY and self.ply == CURRENT_PLAYER) then + setTargetInput(key, true) + end + end) + + hook.add("inputReleased", "inputRelease", function(key) + if (self.ply ~= NULL_ENTITY and self.ply == CURRENT_PLAYER) then + setTargetInput(key, false) + end + end) + + net.receive("changePlayer", function() + self.ply = net.readEntity() + end) + end + +end + +if SERVER then + function InputController:setPlayer(ply) + self.ply = ply + net.start("changePlayer") + net.writeEntity(ply or NULL_ENTITY) + net.send() + end + + function InputController:update() + for key in pairs(self.config) do + if self[key] then + local pressSpeed = self.config[key].press or 1 + local releaseSpeed = self.config[key].release or 1 + + self[key].value = math.lerp(self[key].target == 1 and pressSpeed or releaseSpeed, self[key].value, + self[key].target) + end + end + end +end + +function InputController:setConfig(config) + self.config = table.copy(config) + + local keyToInputName = {} + + for key, value in pairs(config) do + keyToInputName[string.upper(value.key)] = { + inputName = key + } + end + + self.keyToInputName = table.copy(keyToInputName) +end diff --git a/koptilnya/engine_rework/math.txt b/koptilnya/engine_rework/math.txt new file mode 100644 index 0000000..9043337 --- /dev/null +++ b/koptilnya/engine_rework/math.txt @@ -0,0 +1,13 @@ +function factorial(number) + local res = 1 + + for i = 1, number do + res = i * res + end + + return res +end + +function polinom(i, n, t) + return factorial(n) / (factorial(i) * factorial(n - i)) * (t ^ i) * (1 - t) ^ (n - i) +end