--@name Torque editor --@author Opti1337 --@shared --@include /koptilnya/libs/render.txt --@include /koptilnya/gui/render_devices/hud.txt --@include /koptilnya/gui/gui.txt --@include /koptilnya/gui/elements/panel.txt --@include /koptilnya/gui/elements/label.txt --@include /koptilnya/gui/elements/shape.txt --@include /koptilnya/gui/elements/button.txt --@include /koptilnya/gui/segoe_mdl2_assets_icons.txt local points = { Vector(0, 0), Vector(100, 0) } local zoom = 1 local zoomBase = 100 local scroll = 0 if SERVER then local user wire.adjustPorts({Seat = "ENTITY"}, {}) local function setUser(ply) user = ply net.start("setUser") net.writeEntity(ply) net.send() end local function resetUser() user = nil net.start("resetUser") net.send() end hook.add("ClientInitialized", "_ClientInitialized", function(ply) net.start("init") net.writeFloat(zoom) net.writeFloat(scroll) net.writeTable(points) net.send(ply) end) net.receive("settings", function(len, ply) zoom = net.readFloat() scroll = net.readFloat() net.start("settings") net.writeFloat(zoom) net.writeFloat(scroll) net.send(find.allPlayers(function(p) return p ~= ply end)) end) net.receive("points", function(len, ply) points = net.readTable() net.start("points") net.writeTable(points) net.send(find.allPlayers(function(p) return p ~= ply end)) end) hook.add("PlayerEnteredVehicle", "_PlayerEnteredVehicle", function(ply, veh) if veh == wire.ports.Seat then setUser(ply) enableHud(ply, true) end end) hook.add("PlayerLeaveVehicle", "_PlayerLeaveVehicle", function(ply, veh) if veh == wire.ports.Seat then resetUser() enableHud(ply, false) end end) return end require("/koptilnya/libs/render.txt") require("/koptilnya/gui/render_devices/hud.txt") require("/koptilnya/gui/gui.txt") require("/koptilnya/gui/elements/panel.txt") require("/koptilnya/gui/elements/label.txt") require("/koptilnya/gui/elements/shape.txt") require("/koptilnya/gui/elements/button.txt") local segoeIcons = require("/koptilnya/gui/segoe_mdl2_assets_icons.txt") local labelsFont = render.createFont("Roboto", 16, 400, true) local numbersFont = render.createFont("Roboto Mono", 20) local scr = {w = 682, h = 512} local padding = 61 local workspace = {x = padding, y = 0, w = scr.w - padding, h = scr.h - padding} local gridSize = Vector(100, 1) local zoomSteps = {0.05, 0.1, 0.25, 0.5, 1, 2, 4} ------------------------------------------------------- setupPermissionRequest({"file.write", "file.read", "input"}, "", false) local cellSize = Vector((workspace.w - 1) / gridSize.x, workspace.h / gridSize.y) local crosshair local user local function sendSettings() net.start("settings") net.writeFloat(zoom) net.writeFloat(scroll) net.send() end local function sendPoints() net.start("points") net.writeTable(points) net.send() end local function cursorIntersectWorkspace(x, y) x = x >= workspace.x and x < workspace.x + workspace.w y = y >= workspace.y and y <= workspace.y + workspace.h return x and y end local function fromWorkspacePos(x, y) return workspace.x + x, workspace.y + workspace.h - 1 - y end local function toWorkspacePos(x, y) return x - workspace.x, workspace.y + workspace.h - 1 - y end local function getNearestGridPoint(x, y) x, y = toWorkspacePos(x, y) x = math.clamp(x, 0, workspace.w - 1) y = math.clamp(y, 0, workspace.h - 1) x = math.round(x / cellSize.x) * cellSize.x y = math.round(y / cellSize.y) * cellSize.y x, y = fromWorkspacePos(x, y) return x, y end local function zoomIn() local zoomId = table.keyFromValue(zoomSteps, zoom) local nextZoom if zoomId < #zoomSteps then nextZoom = zoomSteps[zoomId + 1] zoom = nextZoom sendSettings() end return nextZoom end local function zoomOut() local zoomId = table.keyFromValue(zoomSteps, zoom) local prevZoom if zoomId > 1 then prevZoom = zoomSteps[zoomId - 1] zoom = prevZoom sendSettings() end return prevZoom end local function scrollUp() scroll = scroll + 1 sendSettings() end local function scrollDown() scroll = math.max(scroll - 1, 0) sendSettings() end local function addPoint() if crosshair ~= nil then local x, y = toWorkspacePos(crosshair.x, crosshair.y) x = math.round(x / cellSize.x) y = y / (workspace.h - 1) * (zoomBase / zoom) + (zoomBase / zoom / 10 * scroll) --- GOVNO ---------------------------- local replaceId for i, point in ipairs(points) do if point.x == x then replaceId = i break end end if replaceId ~= nil then points[replaceId] = Vector(x, y) else table.insert(points, Vector(x, y)) end table.sortByMember(points, "x", true) sendPoints() --- KONEC GOVNA ---------------------- end end local function removePoint() if crosshair ~= nil then local x, y = toWorkspacePos(crosshair.x, crosshair.y) x = math.round(x / cellSize.x) y = y / (workspace.h - 1) * (zoomBase / zoom) + (zoomBase / zoom / 10 * scroll) --- GOVNO ---------------------------- local replaceId for i, point in ipairs(points) do if point.x == x then replaceId = i break end end if replaceId ~= nil then table.remove(points, replaceId) end table.sortByMember(points, "x", true) sendPoints() --- KONEC GOVNA ---------------------- end end local function getYAt(t) if #points == 0 then return 0 end if t == 0 then return points[1].y elseif t == 100 then return points[#points].y else local segment local prevPoint for k, point in pairs(points) do if t <= point.x then segment = {prevPoint, point} break end prevPoint = point end return math.lerp((t - segment[1].x) / (segment[2].x - segment[1].x), segment[1].y, segment[2].y) end end local function exportPoints(asE2Array) if not hasPermission("file.write") then return end asE2Array = asE2Array or false local result = "" local prefix = asE2Array and "array(" or "{" local suffix = asE2Array and ")" or "}" local vecSyntax = asE2Array and "vec2" or "Vector" for k, v in pairs(points) do result = result .. "\t" .. vecSyntax .. "(" .. (v.x / 100) .. ", " .. v.y .. ")" if k ~= #points then result = result .. ",\n" end end result = string.format("%s\n%s\n%s", prefix, result, suffix) file.write("torque_export_result.txt", result) end local function exportTorqueMap() if not hasPermission("file.write") then return end local result = "" for i = 0, 100 do result = result .. "\t" .. getYAt(i) if i ~= 100 then result = result .. ",\n" end end result = "array(\n" .. result result = result .. "\n)" file.write("torque_export_result.txt", result) end local renderDevice = RenderDeviceHUD:new() local scrW, scrH = 1920, 1080 local gui = GUI:new(renderDevice) local w, h = 190, 226 local panel = EPanel:new() panel:setTitle("Torque Editor") panel:setDraggable(false) panel:setMinimizable(false) panel:setCloseable(false) panel:setPos(scrW - w - 20, scrH - h - 20) panel:setSize(w, h) gui:add(panel) local importButton = EButton:new() importButton:setPos(11, 43) importButton:setSize(w - 22, 24) importButton:setText("Import") importButton.onMousePressed = function(_, x, y, key, keyName) if keyName == "MOUSE1" then end end panel:addChild(importButton) local dividerShape = EShape:new() dividerShape:setPos(11, 82) dividerShape:setSize(w - 22, 1) dividerShape:setColor(Color(60, 60, 60)) panel:addChild(dividerShape) local exportLabel = ELabel:new() exportLabel:setPos(11, 97) exportLabel:setFont(GUI.fonts.mainBold) exportLabel:setText("Export as") panel:addChild(exportLabel) local pointsArrayLabel = ELabel:new() pointsArrayLabel:setPos(11, 126) pointsArrayLabel:setText("Points array") pointsArrayLabel:setColorScheme(Color(200, 200, 200)) panel:addChild(pointsArrayLabel) local exportE2ArrayButton = EButton:new() exportE2ArrayButton:setPos(w - 91, 123) exportE2ArrayButton:setText("E2") exportE2ArrayButton:setSize(35, 24) exportE2ArrayButton.onMousePressed = function(_, x, y, key, keyName) if keyName == "MOUSE1" then exportPoints(true) end end panel:addChild(exportE2ArrayButton) local exportSFArrayButton = EButton:new() exportSFArrayButton:setPos(w - 46, 123) exportSFArrayButton:setText("SF") exportSFArrayButton:setSize(35, 24) exportSFArrayButton.onMousePressed = function(_, x, y, key, keyName) if keyName == "MOUSE1" then exportPoints() end end panel:addChild(exportSFArrayButton) local exportMapButton = EButton:new() exportMapButton:setPos(11, 157) exportMapButton:setText("Torque map") exportMapButton:setSize(w - 22, 24) exportMapButton.onMousePressed = function(_, x, y, key, keyName) if keyName == "MOUSE1" then exportTorqueMap() end end panel:addChild(exportMapButton) local pointsCountLabel = ELabel:new() pointsCountLabel:setPos(11, 200) pointsCountLabel:setText("Points count: " .. #points) panel:addChild(pointsCountLabel) hook.add("render", "_render", function() local cursorX, cursorY if isValid(user) then cursorX, cursorY = render.cursorPos(user) end local x, y --- Background x, y = fromWorkspacePos(0, workspace.h - 1) render.setColor(Color(15, 15, 15)) render.drawRectFast(x, y, workspace.w, workspace.h) render.setColor(Color(40, 40, 40)) render.setFont(labelsFont) --- Throttle axis label x, y = fromWorkspacePos(30, 31) render.drawRectFast(x, y, 20, 1) render.drawSimpleText(x + 22, y - 8, "RPM") --- Torque axis label x, y = fromWorkspacePos(30, 50) render.drawRectFast(x, y, 1, 20) render.drawRotatedSimpleText(x - 8, y - 2, "Torque", -90) render.setColor(Color(255, 255, 255)) --- Throttle axis x, y = fromWorkspacePos(0, 0) render.drawRectFast(x, y, workspace.w, 1) --- Torque axis x, y = fromWorkspacePos(0, workspace.h - 1) render.drawRectFast(x, y, 1, workspace.h) --- Axes lines & numbers for i = 1, 10 do --- Throttle axis local segmentSize = (workspace.w - 1) / 10 x, y = fromWorkspacePos(segmentSize * i, 0) local length = 20 local text = tostring(math.remap(i, 0, 10, 1000, 7000)) -- tostring(i * 10) render.drawRect(x, y - 20, 1, 20) render.drawRect(x - segmentSize / 2, y - 10, 1, 10) render.setFont(numbersFont) render.drawSimpleText(x - (9 * #text), y, text) --- Torque axis segmentSize = (workspace.h - 1) / 10 x, y = fromWorkspacePos(0, segmentSize * i) text = tostring(zoomBase / zoom / 10 * (i + scroll)) render.drawRect(x, y, 20, 1) render.drawRect(x, y + segmentSize / 2, 10, 1) render.setFont(numbersFont) render.drawSimpleText(x - (10 * #text), y - 2, text) end --- Zero x, y = fromWorkspacePos(-40, 0) render.setFont(numbersFont) render.drawSimpleText(x, y, "1000") if cursorX ~= nil and cursorIntersectWorkspace(cursorX, cursorY) then x, y = fromWorkspacePos(0, workspace.h - 1) cursorX = getNearestGridPoint(cursorX, cursorY) cursorY = math.clamp(cursorY, workspace.y, workspace.y + workspace.h - 1) crosshair = {x = cursorX, y = cursorY} render.setColor(Color(60, 60, 60, 200)) render.drawRect(x, cursorY, workspace.w, 1) render.drawRect(cursorX, y, 1, workspace.h) else crosshair = nil end render.setColor(Color(255, 150, 50)) local prevPointData for _, point in pairs(points) do x, y = fromWorkspacePos(cellSize.x * point.x, (workspace.h - 1) / (zoomBase / zoom) * point.y - ((workspace.h - 1) / 10 * scroll)) if prevPointData ~= nil then render.drawLine(prevPointData.x, prevPointData.y, x, y) end render.drawRect(x - 1, y - 1, 3, 3) prevPointData = {point = point, x = x, y = y} end local t = (math.cos(timer.curtime() * 0.5) + 1) / 2 * 100 x, y = fromWorkspacePos(cellSize.x * t, (workspace.h - 1) / (zoomBase / zoom) * getYAt(t) - ((workspace.h - 1) / 10 * scroll)) render.setColor(Color(0, 0, 255, 180)) render.drawRect(x - 3, y - 3, 7, 7) render.drawRect(x, workspace.y, 1, workspace.h) end) net.receive("setUser", function() net.readEntity(function(ent) user = ent end) end) net.receive("resetUser", function() user = nil end) net.receive("init", function() zoom = net.readFloat() scroll = net.readFloat() points = net.readTable() end) net.receive("settings", function() zoom = math.round(net.readFloat() * 100) / 100 scroll = math.round(net.readFloat() * 100) / 100 end) net.receive("points", function() points = net.readTable() pointsCountLabel:setText("Points count: " .. #points) end) hook.add("hudconnected", "_hudconnected", function() -- renderDevice:setPlayer() if not hasPermission("file.write") or not hasPermission("file.read") then sendPermissionRequest() end end) hook.add("huddisconnected", "_huddisconnected", function() end) hook.add("inputPressed", "_inputPressed", function(button) if not hasPermission("input") then return end if player() == user then local keyName = input.getKeyName(button) if keyName == "t" then input.enableCursor(!input.getCursorVisible()) elseif keyName == "MOUSE1" then if not input.getCursorVisible() then addPoint() pointsCountLabel:setText("Points count: " .. #points) end elseif keyName == "MOUSE2" then if not input.getCursorVisible() then removePoint() pointsCountLabel:setText("Points count: " .. #points) end elseif keyName == "MWHEELUP" then if input.isShiftDown() then zoomIn() else scrollUp() end elseif keyName == "MWHEELDOWN" then if input.isShiftDown() then zoomOut() else scrollDown() end end end end)