commit a7466b2e68afb4b367e938025d06586d13024400 Author: Fierelier Date: Thu Mar 11 12:02:13 2021 +0100 first commit 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 @@ + + + + +