From a7466b2e68afb4b367e938025d06586d13024400 Mon Sep 17 00:00:00 2001 From: Fierelier Date: Thu, 11 Mar 2021 12:02:13 +0100 Subject: [PATCH] first commit --- MinHeap.lua | 93 + ai.client.lua | 189 + ai.server.lua | 17 + client.lua | 87 + gps.lua | 109 + linedrawer.lua | 128 + meta.xml | 17 + overlay.fx | 20 + util.lua | 89 + vehiclenodes.lua | 30974 +++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 31723 insertions(+) create mode 100644 MinHeap.lua create mode 100644 ai.client.lua create mode 100644 ai.server.lua create mode 100644 client.lua create mode 100644 gps.lua create mode 100644 linedrawer.lua create mode 100644 meta.xml create mode 100644 overlay.fx create mode 100644 util.lua create mode 100644 vehiclenodes.lua diff --git a/MinHeap.lua b/MinHeap.lua new file mode 100644 index 0000000..59d716b --- /dev/null +++ b/MinHeap.lua @@ -0,0 +1,93 @@ +MinHeap = {} +MinHeap.__index = MinHeap + +local floor = math.floor + +function MinHeap.new() + return setmetatable({ n = 0 }, MinHeap) +end + +function MinHeap:insertvalue(num) + self[self.n] = num + self.n = self.n + 1 + + local child = self.n - 1 + local parent, temp + while child > 0 do + parent = floor((child - 1)/2) + if self[parent] <= self[child] then + break + end + temp = self[parent] + self[parent] = self[child] + self[child] = temp + child = parent + end + return true +end + +function MinHeap:findindex(num, root) + root = root or 0 + if root >= self.n or num < self[root] then + return false + end + if num == self[root] then + return root + end + return self:findindex(num, root*2 + 1) or self:findindex(num, root*2 + 2) +end + +function MinHeap:deleteindex(index) + if index < 0 or index >= self.n then + return false + end + local deleted = self[index] + self[index] = self[self.n-1] + self[self.n-1] = nil + self.n = self.n - 1 + + local parent = index + local child, temp + while true do + child = parent*2 + 1 + if child >= self.n then + break + end + if child < self.n - 1 and self[child+1] < self[child] then + child = child + 1 + end + if self[parent] <= self[child] then + break + end + temp = self[parent] + self[parent] = self[child] + self[child] = temp + parent = child + end + return deleted +end + +function MinHeap:deletevalue(num) + local index = self:findindex(num) + if not index then + return false + end + return self:deleteindex(index) +end + +function MinHeap:size() + return self.n +end + +function MinHeap:empty() + return self.n == 0 +end + +function MinHeap:__tostring(index, depth) + index = index or 0 + depth = depth or 0 + if index >= self.n then + return '' + end + return (' '):rep(depth) .. tostring(self[index]) .. '\n' .. self:__tostring(index*2+1, depth+1) .. self:__tostring(index*2+2, depth+1) +end diff --git a/ai.client.lua b/ai.client.lua new file mode 100644 index 0000000..d5c7bcf --- /dev/null +++ b/ai.client.lua @@ -0,0 +1,189 @@ +function aiGetClosestNode(x,y,z,path) + local cDist = nil + local cNode = nil + local cNodeID = nil + + for i,node in ipairs(path) do + local dist = getDistanceBetweenPoints3D(x,y,z,node.x,node.y,node.z) + if cDist == nil then + cDist = dist + cNodeID = i + cNode = node + end + + if dist < cDist then + cDist = dist + cNodeID = i + cNode = node + end + end + + return cNodeID,cNode +end + +function aiGetNextNode(path,nodeID,minDist) + minDist = minDist or 0 + local prevX = path[nodeID].x + local prevY = path[nodeID].y + local prevZ = path[nodeID].z + local dist = 0 + nodeID = nodeID + 1 + local length = #path + while nodeID <= length do + local x = path[nodeID].x + local y = path[nodeID].y + local z = path[nodeID].z + dist = dist + getDistanceBetweenPoints3D(x,y,z,prevX,prevY,prevZ) + if dist >= minDist then + return nodeID,path[nodeID] + end + prevX = x + prevY = y + prevZ = z + nodeID = nodeID + 1 + end + + nodeID = length + return nodeID,path[nodeID] +end + +function handleGpsAi(driver) + local gx = getElementData(driver,"gps-ai.x") + local gy = getElementData(driver,"gps-ai.y") + local gz = getElementData(driver,"gps-ai.z") + local veh = getPedOccupiedVehicle(driver) + local x,y,z = getElementPosition(veh) + local rx,ry,rz = getElementRotation(veh) + local speed = getElementSpeed(veh) + + if not getElementData(driver,"gps-ai.path") then + local path = calculatePathByCoords(x,y,z,gx,gy,gz) + if not path then return end + setElementData(driver,"gps-ai.path",path,false) + end + + local path = getElementData(driver,"gps-ai.path") + local closestNodeID,closestNode = aiGetClosestNode(x,y,z,path) + local checkNodeOneID,checkNodeOne = aiGetNextNode(path,closestNodeID,20 + ((speed * 0.15) * (speed * 0.01))) + local checkNodeTwoID,checkNodeTwo = aiGetNextNode(path,closestNodeID,20 + ((speed * 0.4) * (speed * 0.01))) + + --[[ hit = processLineOfSight(x,y,z,checkNodeOne.x,checkNodeOne.y,checkNodeOne.z + 0.5,true,false,false,true,false,false,false,false,nil,false,false) + if hit then + checkNodeOneID,checkNodeOne = aiGetNextNode(path,closestNodeID) + end ]]-- + + local reqzOne = findRotation(x,y,checkNodeOne.x,checkNodeOne.y) + local reqzTwo = findRotation(x,y,checkNodeTwo.x,checkNodeTwo.y) + + reqzOneLocal = rz - reqzOne + if reqzOneLocal > 180 then reqzOneLocal = reqzOneLocal - 360 end + if reqzOneLocal < -180 then reqzOneLocal = reqzOneLocal + 360 end + + reqzTwoLocal = rz - reqzTwo + if reqzTwoLocal > 180 then reqzTwoLocal = reqzTwoLocal - 360 end + if reqzTwoLocal < -180 then reqzTwoLocal = reqzTwoLocal + 360 end + + local steeringAngleOne = reqzOneLocal + if steeringAngleOne < 0 then + steeringAngleOne = 0 - steeringAngleOne + end + steeringAmountOne = steeringAngleOne / 180 + + local steeringAngleTwo = reqzTwoLocal + if steeringAngleTwo < 0 then + steeringAngleTwo = 0 - steeringAngleTwo + end + steeringAmountTwo = steeringAngleTwo / 180 + + local brake = false + --[[ if steeringAngleOne > steeringAngleTwo then + regzTwo = regzOne + regzTwoLocal = regzOneLocal + steeringAngleTwo = steeringAngleOne + steeringAmountTwo = steeringAmountOne + brake = true + end ]]-- + + local maxSpeed = getElementData(driver,"gps-ai.maxSpeed") + if steeringAngleTwo > 10 then + local owo = steeringAmountTwo * 2.5 + if owo > 1 then owo = 1 end + local nMaxSpeed = 15 + (130 * (1 - owo)) + if nMaxSpeed < maxSpeed then maxSpeed = nMaxSpeed end + end + + if brake then maxSpeed = maxSpeed * 0.5 end + + if speed < maxSpeed then + setPedControlState(driver,"accelerate",true) + setPedAnalogControlState(driver,"brake_reverse",0) + else + setPedControlState(driver,"accelerate",false) + end + + if speed > maxSpeed + 5 then + setPedAnalogControlState(driver,"brake_reverse",0.8) + end + + local currentSteer = getPedAnalogControlState(driver,"vehicle_right") - getPedAnalogControlState(driver,"vehicle_left") + local steerSpeed = 0.2 + + if reqzOneLocal < 0 then + currentSteer = currentSteer - steerSpeed + end + + if reqzOneLocal > 0 then + currentSteer = currentSteer + steerSpeed + end + steerLimit = steeringAngleOne / 10 + + if steerLimit < 0.4 then steerLimit = 0.4 end + if steerLimit > 1 then steerLimit = 1 end + if currentSteer > steerLimit then currentSteer = steerLimit end + if currentSteer < 0 - steerLimit then currentSteer = 0 - steerLimit end + + if currentSteer > 0 then + setPedAnalogControlState(driver,"vehicle_left",0) + setPedAnalogControlState(driver,"vehicle_right",currentSteer) + end + + if currentSteer < 0 then + setPedAnalogControlState(driver,"vehicle_right",0) + setPedAnalogControlState(driver,"vehicle_left",0 - currentSteer) + end + + -- DEBUG -- + local resx,resy = guiGetScreenSize() + dxDrawLine3D(x,y,z,checkNodeOne.x,checkNodeOne.y,checkNodeOne.z + 1,tocolor(255,0,0,128),10) + dxDrawLine3D(x,y,z,checkNodeTwo.x,checkNodeTwo.y,checkNodeTwo.z + 1,tocolor(255,255,0,128),10) + + local debugText = "" + debugText = debugText .. "Required Z 1: " ..reqzOne + debugText = debugText .. "\nRequired Z 2: " ..reqzTwo + debugText = debugText .. "\n" + debugText = debugText .. "\nRequired Z 1 - LOCAL: " ..reqzOneLocal + debugText = debugText .. "\nMax speed: " ..maxSpeed.. " (" ..steeringAmountTwo.. ")" + local px,py,pz = getElementPosition(driver) + local dbx,dby,dbz = getScreenFromWorldPosition(px,py,pz + 1) + dxDrawText(debugText,dbx + 1,dby + 1,dbx + 1,dby + 1,tocolor(0,0,0),1,"default-bold","center","center",false,true,true) + dxDrawText(debugText,dbx,dby,dbx,dby,tocolor(255,255,0),1,"default-bold","center","center",false,true,true) +end + +function handleGpsAiFrame() + for _,ped in ipairs(getElementsByType("ped")) do + if getElementData(ped,"gps-ai.x") then + handleGpsAi(ped) + end + end +end + +addEventHandler("onClientRender",root,handleGpsAiFrame) + +function getElementSpeed(theElement) + return (Vector3(getElementVelocity(theElement)) * 180).length +end + +function findRotation( x1, y1, x2, y2 ) + local t = -math.deg( math.atan2( x2 - x1, y2 - y1 ) ) + return t < 0 and t + 360 or t +end \ No newline at end of file diff --git a/ai.server.lua b/ai.server.lua new file mode 100644 index 0000000..79895af --- /dev/null +++ b/ai.server.lua @@ -0,0 +1,17 @@ +addCommandHandler('drive', function(player, command, tox, toy, toz, maxSpeed) + if not getPedOccupiedVehicle(player) or getPedOccupiedVehicleSeat(player) ~= 0 then + outputChatBox("You need to be driving a vehicle.",player,255,0,0) + --return + end + + x,y,z = getElementPosition(player) + veh = getPedOccupiedVehicle(player) + maxSpeed = maxSpeed or 50 + driver = createPed(107,x,y,z) + setElementData(driver,"gps-ai.x",tox) + setElementData(driver,"gps-ai.y",toy) + setElementData(driver,"gps-ai.z",toz) + setElementData(driver,"gps-ai.maxSpeed",tonumber(maxSpeed)) + warpPedIntoVehicle(player,veh,1) + warpPedIntoVehicle(driver,veh,0) +end) \ No newline at end of file diff --git a/client.lua b/client.lua new file mode 100644 index 0000000..f160d0c --- /dev/null +++ b/client.lua @@ -0,0 +1,87 @@ +local floor = math.floor + +addCommandHandler('path', + function(command, node1, node2) + if not tonumber(node1) or not tonumber(node2) then + outputChatBox("Usage: /path node1 node2", 255, 0, 0) + return + end + local path = server.calculatePathByNodeIDs(tonumber(node1), tonumber(node2)) + if not path then + outputConsole('No path found') + return + end + server.spawnPlayer(getLocalPlayer(), path[1].x, path[1].y, path[1].z) + fadeCamera(true) + setCameraTarget(getLocalPlayer()) + + removeLinePoints ( ) + table.each(getElementsByType('marker'), destroyElement) + for i,node in ipairs(path) do + createMarker(node.x, node.y, node.z, 'corona', 5, 50, 0, 255, 200) + addLinePoint ( node.x, node.y ) + end + end +) +addCommandHandler('path2', + function(command, tox, toy, toz) + if not tonumber(tox) or not tonumber(toy) then + outputChatBox("Usage: /path2 x y z (z is optional)", 255, 0, 0) + return + end + local x,y,z = getElementPosition(getLocalPlayer()) + local path = calculatePathByCoords(x, y, z, tox, toy, toz) + if not path then + outputConsole('No path found') + return + end + + removeLinePoints ( ) + table.each(getElementsByType('marker'), destroyElement) + for i,node in ipairs(path) do + createMarker(node.x, node.y, node.z, 'corona', 5, 50, 0, 255, 200) + addLinePoint ( node.x, node.y ) + end + end +) + +local function getAreaID(x, y) + return math.floor((y + 3000)/750)*8 + math.floor((x + 3000)/750) +end + +local function getNodeByID(db, nodeID) + local areaID = floor(nodeID / 65536) + return db[areaID][nodeID] +end + +--[[ +addEventHandler('onClientRender', getRootElement(), + function() + local db = vehicleNodes + + local camX, camY, camZ = getCameraMatrix() + local x, y, z = getElementPosition(getLocalPlayer()) + local areaID = getAreaID(x, y) + local drawn = {} + for id,node in pairs(db[areaID]) do + if getDistanceBetweenPoints3D(x, y, z, node.x, node.y, z) < 300 then + --[/[ + local screenX, screenY = getScreenFromWorldPosition(node.x, node.y, node.z) + if screenX then + dxDrawText(tostring(id), screenX - 10, screenY - 5) + end + --]/] + --[/[ + for neighbourid,distance in pairs(node.neighbours) do + if not drawn[neighbourid .. '-' .. id] then + local neighbour = getNodeByID(db, neighbourid) + dxDrawLine3D(node.x, node.y, node.z + 1, neighbour.x, neighbour.y, neighbour.z + 1, tocolor(0, 0, 200, 255), 3) + drawn[id .. '-' .. neighbourid] = true + end + end + --]/] + end + end + end +) +--]] diff --git a/gps.lua b/gps.lua new file mode 100644 index 0000000..0ae4da6 --- /dev/null +++ b/gps.lua @@ -0,0 +1,109 @@ +local root = getRootElement() +local floor = math.floor + +local allowedRPC = { + calculatePathByCoords = true, + calculatePathByNodeIDs = true, + spawnPlayer = true +} + +local function getAreaID(x, y) + return floor((y + 3000)/750)*8 + floor((x + 3000)/750) +end + +local function getNodeByID(db, nodeID) + local areaID = floor(nodeID / 65536) + return db[areaID][nodeID] +end + +local function findNodeClosestToPoint(db, x, y, z) + local areaID = getAreaID(x, y) + local minDist, minNode + local nodeX, nodeY, dist + for id,node in pairs(db[areaID]) do + nodeX, nodeY = node.x, node.y + dist = (x - nodeX)*(x - nodeX) + (y - nodeY)*(y - nodeY) + if not minDist or dist < minDist then + minDist = dist + minNode = node + end + end + return minNode +end + +local function calculatePath(db, nodeFrom, nodeTo) + local next = next + + local g = { [nodeFrom] = 0 } -- { node = g } + local hcache = {} -- { node = h } + local parent = {} -- { node = parent } + local openheap = MinHeap.new() + + local function h(node) + if hcache[node] then + return hcache[node] + end + local x, y, z = node.x - nodeTo.x, node.y - nodeTo.y, node.z - nodeTo.z + hcache[node] = x*x + y*y + z*z + return hcache[node] + end + local nodeMT = { + __lt = function(a, b) + return g[a] + h(a) < g[b] + h(b) + end, + __le = function(a, b) + if not g[a] or not g[b] then + outputConsole(debug.traceback()) + end + return g[a] + h(a) <= g[b] + h(b) + end + } + setmetatable(nodeFrom, nodeMT) + openheap:insertvalue(nodeFrom) + + local current + while not openheap:empty() do + current = openheap:deleteindex(0) + if current == nodeTo then + break + end + + local successors = {} + for id,distance in pairs(current.neighbours) do + local successor = getNodeByID(db, id) + local successor_g = g[current] + distance*distance + if not g[successor] or g[successor] > successor_g then + setmetatable(successor, nodeMT) + + g[successor] = successor_g + openheap:insertvalue(successor) + parent[successor] = current + end + end + end + + if current == nodeTo then + local path = {} + repeat + table.insert(path, 1, current) + current = parent[current] + until not current + return path + else + return false + end +end + +function calculatePathByCoords(x1, y1, z1, x2, y2, z2) + return calculatePath(vehicleNodes, findNodeClosestToPoint(vehicleNodes, x1, y1, z1), findNodeClosestToPoint(vehicleNodes, x2, y2, z2)) +end + +function calculatePathByNodeIDs(node1, node2) + node1 = getNodeByID(vehicleNodes, node1) + node2 = getNodeByID(vehicleNodes, node2) + if node1 and node2 then + return calculatePath(vehicleNodes, node1, node2) + else + return false + end +end \ No newline at end of file diff --git a/linedrawer.lua b/linedrawer.lua new file mode 100644 index 0000000..ef7df0f --- /dev/null +++ b/linedrawer.lua @@ -0,0 +1,128 @@ +local ENABLE_FAILISH_ATTEMPT_AT_ANTI_ALIASING = false + +local OVERLAY_WIDTH = 256 +local OVERLAY_HEIGHT = 256 +local OVERLAY_LINE_WIDTH = 5 +local OVERLAY_LINE_COLOR = tocolor ( 0, 200, 0, 255 ) +local OVERLAY_LINE_AA = tocolor ( 0, 200, 0, 200 ) + +local linePoints = { } +local renderStuff = { } + +function removeLinePoints ( ) + linePoints = { } + for name, data in pairs ( renderStuff ) do + unloadTile ( name ) + end +end + +function addLinePoint ( posX, posY ) + -- Calculate the row and column of the radar tile we will be targeting + local row = 11 - math.floor ( ( posY + 3000 ) / 500 ) + local col = math.floor ( ( posX + 3000 ) / 500 ) + + -- If it's off the map, don't bother + if row < 0 or row > 11 or col < 0 or col > 11 then + return false + end + + -- Check the start position of the tile + local startX = col * 500 - 3000 + local startY = 3000 - row * 500 + + -- Now get the tile position (We don't want to calculate this for every point on render) + local tileX = ( posX - startX ) / 500 * OVERLAY_WIDTH + local tileY = ( startY - posY ) / 500 * OVERLAY_HEIGHT + + -- Now calulcate the ID and get the name of the tile + local id = col + row * 12 + local name = string.format ( "radar%02d", id ) + + -- Make sure the line point table exists + if not linePoints [ name ] then + linePoints [ name ] = { } + end + + -- Now add this point + table.insert ( linePoints[name], { posX = tileX, posY = tileY } ) + + -- Success! + return true +end + +function loadTile ( name ) + -- Create our fabulous shader. Abort on failure + local shader = dxCreateShader ( "overlay.fx" ) + if not shader then + return false + end + + -- Create a render target. Again, abort on failure (don't forget to delete the shader!) + local rt = dxCreateRenderTarget ( OVERLAY_WIDTH, OVERLAY_HEIGHT, true ) + if not rt then + destroyElement ( shader ) + return false + end + + -- Mix 'n match + dxSetShaderValue ( shader, "gOverlay", rt ) + + -- Start drawing + dxSetRenderTarget ( rt ) + + -- Get the points involved, and get the starting position + local points = linePoints [ name ] + local prevX, prevY = points [ 1 ].posX, points [ 1 ] .posY + + -- Loop through all points we have to draw, and draw them + for index, point in ipairs ( points ) do + local newX = point.posX + local newY = point.posY + + if ENABLE_FAILISH_ATTEMPT_AT_ANTI_ALIASING then + dxDrawLine ( prevX - 1, prevY - 1, newX - 1, newY - 1, OVERLAY_LINE_AA, OVERLAY_LINE_WIDTH ) + dxDrawLine ( prevX + 1, prevY - 1, newX + 1, newY - 1, OVERLAY_LINE_AA, OVERLAY_LINE_WIDTH ) + dxDrawLine ( prevX - 1, prevY + 1, newX - 1, newY + 1, OVERLAY_LINE_AA, OVERLAY_LINE_WIDTH ) + dxDrawLine ( prevX + 1, prevY + 1, newX + 1, newY + 1, OVERLAY_LINE_AA, OVERLAY_LINE_WIDTH ) + end + + dxDrawLine ( prevX, prevY, newX, newY, OVERLAY_LINE_COLOR, OVERLAY_LINE_WIDTH ) + + prevX = newX + prevY = newY + end + + -- Now let's show our fabulous work to the commoners! + engineApplyShaderToWorldTexture ( shader, name ) + + -- Store the stuff in memories + renderStuff [ name ] = { shader = shader, rt = rt } + + -- We won + return true +end + +function unloadTile ( name ) + destroyElement ( renderStuff[name].shader ) + destroyElement ( renderStuff[name].rt ) + renderStuff[name] = nil + return true +end + +addEventHandler ( "onClientHUDRender", getRootElement ( ), + function ( ) + local visibleTileNames = table.merge ( engineGetVisibleTextureNames ( "radar??" ), engineGetVisibleTextureNames ( "radar???" ) ) + + for name, data in pairs ( renderStuff ) do + if not table.find ( visibleTileNames, name ) then + unloadTile ( name ) + end + end + + for index, name in ipairs ( visibleTileNames ) do + if linePoints [ name ] and not renderStuff [ name ] then + loadTile ( name ) + end + end + end +) diff --git a/meta.xml b/meta.xml new file mode 100644 index 0000000..5c80078 --- /dev/null +++ b/meta.xml @@ -0,0 +1,17 @@ + + + + +