Jump to content

Need help with DX, positions, rotations and tables


Recommended Posts

Hey!

I would like to create a junkyard script where you can search for items, and to search for an item you would have to play a minigame, but I bumped into problems and I don't know

- how to get a random screen position inside given positions (for example I have a box in the middle of the screen and I would like to get a random position inside that box only)
- how to rotate every image in other angle created from a table
Like this:

http://imgur.com/2Pq43OB


- how to move the junk items (images) seperately, I would like to make the minigame work like when you hold the left mouse button the junk items will move in the opposite way, so it's like pushing out the junk from the middle of the box while you are holding mouse button.
Like this:

http://imgur.com/pxgRpRi

So I would like people to play this kind of minigame when they are looking for items in a junkyard, and inside the yellow box there would be the item that they have found, if they found anything. Hope you can understand me and what I am trying to do and you can help me. Can't think about any good way to start it, here's my current code:

 

CLIENT:

local sx, sy = guiGetScreenSize()
local junkSize = 128
local junkImageDistance = 50
local junkPositionX, junkPositionY = sx/2-junkSize/2, sy/2-junkSize/2

local junkyardCollision = nil
local searchTime = 1000
local searchTimeMultiplier = 1
local minimumSearchTimeMultiplier, maximumSearchTimeMultiplier = 5, 15

function createJunkyards()
	for k,v in ipairs(junkyards) do
		junkyardCollision = createColCuboid(v[1], v[2], v[3]-0.85, v[4], v[5], v[6])
	end
end
addEventHandler("onClientResourceStart", getResourceRootElement(getThisResource()), createJunkyards)

local junks = {
	-- image, size
	{"burgershot-bag", 128},
	{"cup", 128},
	{"boot", 256},
	{"bin-bag", 256},
	{"apple", 128},
	{"paper", 128},
	{"bottle", 128},
	{"glass", 128},
	{"tin-can", 128},
}

local junkRotation = 0
function rotateJunk()
	for k, v in ipairs(junks) do
		local rot = math.random(1,360)
		junkRotation = rot
	end
end
rotateJunk()

function renderJunk()
	for k, v in ipairs(junks) do
		dxDrawImage(junkPositionX+k*junkImageDistance, junkPositionY+k*junkImageDistance, v[2], v[2], "junkyard/files/" .. v[1] ..".png", junkRotation, 0, 0, tocolor(255,255,255,255), false)
	end
end
addEventHandler("onClientRender", getRootElement(), renderJunk)

function startSearching()
	if junkyardCollision and isElement(junkyardCollision) then
		if isElementWithinColShape(localPlayer, junkyardCollision) then
			searchTimeMultiplier = math.random(minimumSearchTimeMultiplier,maximumSearchTimeMultiplier)
			local finalSearchTime = searchTime*searchTimeMultiplier
			
			triggerServerEvent("syncSearchingFromServer", localPlayer, localPlayer, finalSearchTime)
			setTimer(function()
				triggerServerEvent("giveFoundJunkItem", localPlayer, localPlayer)
			end,finalSearchTime,1)
		else
			exports["cg_notifications"]:addNotification("Ezt a parancsot csak szeméttelepen tudod használni!", "error")
		end
	end
end
addCommandHandler("turkal", startSearching)

 

GLOBALS (shared):

junkyards = {
	-- x, y, z, width, length, height, 
	{2164.6706542969, -1504.7607421875, 23.984375, 10, 10, 2}
}

items = {
	-- item id, chance (%), amount
	{13, 20, 1}, -- cigar
	{25, 60, 1}, -- rope
	{67, 40, 1}, -- lighter
	{68, 5, 100}, -- graffiti patron
}

 

SERVER:

function syncSearchingFromServer(player, searchTime)
	if player and isElement(player) and getElementType(player) == "player" then
		setPedAnimation(player, "BOMBER", "BOM_Plant_Loop", searchTime, true, false, false, false)
	end
end
addEvent("syncSearchingFromServer", true)
addEventHandler("syncSearchingFromServer", getRootElement(), syncSearchingFromServer)

function giveFoundJunkItem(player)
	if player and isElement(player) and getElementType(player) == "player" then
		local foundItem = math.random(1,#items)
		local currentChance = math.random(1,100)
		
		if currentChance <= items[foundItem][2] then
			outputChatBox("Item a táblából: " .. tostring(foundItem))
			outputChatBox("Saját Esély: " .. tostring(currentChance))
			outputChatBox(" ")
			outputChatBox("Item: " .. tostring(items[foundItem][1]))
			outputChatBox("Item Esély: " .. tostring(items[foundItem][2]))
			outputChatBox("Item Mennyiség: " .. tostring(items[foundItem][3]))
			outputChatBox(" ")
			outputChatBox("megkaptad")
		else
			exports["cg_notifications"]:addNotification(player, "Sajnos semmit sem találtál.", "info")
			return
		end
	end
end
addEvent("giveFoundJunkItem", true)
addEventHandler("giveFoundJunkItem", getRootElement(), giveFoundJunkItem)

Thank you for your help in advance!

Link to comment
  • Moderators

Alright, after severals hours, I managed to do this (game resolution: 1024x768, used my own images):

902cfe6b6698c7b6921b38a0c098b047.gif

MP4 (HQ)

Maybe we can improve it using vectors I don't know but I'm not confortable with them.

Here is the client code (the hard part is done. I left the debug drawings but you can remove everything that has the "-- debug" comment)

junkyards = {
	-- x, y, z, width, length, height, 
	{2164.6706542969, -1504.7607421875, 23.984375, 10, 10, 2}
}

items = {
	-- item id, chance (%), amount
	{13, 20, 1}, -- cigar
	{25, 60, 1}, -- rope
	{67, 40, 1}, -- lighter
	{68, 5, 100}, -- graffiti patron
}

local sx, sy = guiGetScreenSize()
local junkSize = sx/2
local junkImageDistance = 50
local junkPositionX, junkPositionY = sx/2-junkSize/2, sy/2-junkSize/2

local junks = {} -- holds all junks images with position, rotation etc
local board = {x = junkPositionX, y = junkPositionY, width = junkSize, height = junkSize}
local mouseRadius = 20 -- how big the mouse's hitbox is (higher = easier to move junks)
local repulseSpeed = 12 -- how fast the junks go away from mouse's hitbox

local junkyardCollision = nil
local searchTime = 1000
local searchTimeMultiplier = 1
local minimumSearchTimeMultiplier, maximumSearchTimeMultiplier = 5, 15

function createJunkyards()
	for k,v in ipairs(junkyards) do
		junkyardCollision = createColCuboid(v[1], v[2], v[3]-0.85, v[4], v[5], v[6])
	end
end
addEventHandler("onClientResourceStart", getResourceRootElement(getThisResource()), createJunkyards)

local junkImages = {
	-- image, size, hitradius
	{"burgershot-bag", 128, 50},
	{"cup", 128, 50},
	{"boot", 256, 100},
	{"bin-bag", 256, 100},
	{"apple", 128, 50},
	{"paper", 128, 50},
	{"bottle", 128, 50},
	{"glass", 128, 50},
	{"tin-can", 128, 50}
}

function createJunks()
	junks = {}
	for k, v in ipairs(junkImages) do
		local size = v[2]
		local rot = math.random(1,360)
		local midX, midY = getRandomPointInCircle(board.x+board.width/2, board.y+board.width/2, board.width/4)
		local x, y = midX - size/2, midY - size/2 -- we want the middle of the image to be placed at that point
		table.insert(junks, {x = x, y = y, size = size, img = v[1], rot = rot, hitradius = v[3]})
	end
end
showCursor(true) -- debug (add it to your command that shows the junk)
bindKey("f5", "down", createJunks) -- debug (F5 to (re)create the junk)

function renderJunk()
	dxDrawRectangle(board.x, board.y, board.width, board.height, tocolor(200, 0, 0, 255)) -- debug (junk board)
	dxDrawCircle(board.x+board.width/2, board.y+board.width/2, board.width/4, 1, 1, 0, 360, tocolor(0, 0, 0, 255)) -- debug (spawn area - black circle)
	
	for k, j in ipairs(junks) do
		local cx, cy = getCursorPosition()
		cx, cy = cx * sx, cy * sy -- absolute cursor position
		local imgCenterX, imgCenterY = j.x+j.size/2, j.y+j.size/2 -- center of the junk's image
		
		dxDrawImage(j.x, j.y, j.size, j.size, "junkyard/files/" ..j.img..".png", j.rot, 0, 0, tocolor(255,255,255,255), false)
		
		dxDrawCircle(imgCenterX, imgCenterY, j.hitradius, 1, 1, 0, 360, tocolor(0, 0, 200, 255)) -- debug (junk's hitbox - blue circle)
		dxDrawCircle(cx, cy, mouseRadius, 1, 1, 0, 360, tocolor(255, 255, 0, 255)) -- debug (mouse's hitbox)
		
		if getKeyState("mouse1") and isCircleInCircle(imgCenterX, imgCenterY, j.hitradius, cx, cy, mouseRadius) then
			-- move it away if it collides with the cursor's hitbox
			local angle = findRotation(imgCenterX, imgCenterY, cx, cy)
			local newX, newY = getPointFromDistanceRotation(imgCenterX, imgCenterY, repulseSpeed, -angle + 180)
			local offsetX, offsetY = imgCenterX - newX, imgCenterY - newY
			j.x, j.y = j.x-offsetX, j.y-offsetY
			
			-- debug:
			local newX, newY = getPointFromDistanceRotation(imgCenterX, imgCenterY, 200, -angle + 180) -- debug
			dxDrawLine(imgCenterX, imgCenterY, newX, newY, tocolor(0, 200, 0, 255)) -- debug (shows direction where the junk is going - green line)
		end
	end
end
addEventHandler("onClientRender", getRootElement(), renderJunk)

function startSearching()
	if junkyardCollision and isElement(junkyardCollision) then
		if isElementWithinColShape(localPlayer, junkyardCollision) then
			searchTimeMultiplier = math.random(minimumSearchTimeMultiplier,maximumSearchTimeMultiplier)
			local finalSearchTime = searchTime*searchTimeMultiplier
			
			triggerServerEvent("syncSearchingFromServer", localPlayer, localPlayer, finalSearchTime)
			setTimer(function()
				triggerServerEvent("giveFoundJunkItem", localPlayer, localPlayer)
			end,finalSearchTime,1)
		else
			exports["cg_notifications"]:addNotification("Ezt a parancsot csak szeméttelepen tudod használni!", "error")
		end
	end
end
addCommandHandler("turkal", startSearching)

-----------------------------------------------------
-- From https://wiki.multitheftauto.com/wiki/DxDrawCircle (was only used for debug drawings)
function dxDrawCircle( posX, posY, radius, width, angleAmount, startAngle, stopAngle, color, postGUI )
	if ( type( posX ) ~= "number" ) or ( type( posY ) ~= "number" ) then
		return false
	end
	local function clamp( val, lower, upper )
		if ( lower > upper ) then lower, upper = upper, lower end
		return math.max( lower, math.min( upper, val ) )
	end
	radius = type( radius ) == "number" and radius or 50
	width = type( width ) == "number" and width or 5
	angleAmount = type( angleAmount ) == "number" and angleAmount or 1
	startAngle = clamp( type( startAngle ) == "number" and startAngle or 0, 0, 360 )
	stopAngle = clamp( type( stopAngle ) == "number" and stopAngle or 360, 0, 360 )
	color = color or tocolor( 255, 255, 255, 200 )
	postGUI = type( postGUI ) == "boolean" and postGUI or false
	if ( stopAngle < startAngle ) then
		local tempAngle = stopAngle
		stopAngle = startAngle
		startAngle = tempAngle
	end
	for i = startAngle, stopAngle, angleAmount do
		local startX = math.cos( math.rad( i ) ) * ( radius - width )
		local startY = math.sin( math.rad( i ) ) * ( radius - width )
		local endX = math.cos( math.rad( i ) ) * ( radius + width )
		local endY = math.sin( math.rad( i ) ) * ( radius + width )
		dxDrawLine( startX + posX, startY + posY, endX + posX, endY + posY, color, width, postGUI )
	end
	return true
end

-- From http://cgp.wikidot.com/circle-to-circle-collision-detection
function isCircleInCircle(x1, y1, r1, x2, y2, r2)
	return math.sqrt( ( x2-x1 ) * ( x2-x1 )  + ( y2-y1 ) * ( y2-y1 ) ) < ( r1 + r2 )
end

-- From https://wiki.multitheftauto.com/wiki/FindRotation
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

-- From https://wiki.multitheftauto.com/wiki/GetPointFromDistanceRotation
function getPointFromDistanceRotation(x, y, dist, angle)
    local a = math.rad(90 - angle);
    local dx = math.cos(a) * dist;
    local dy = math.sin(a) * dist;
    return x+dx, y+dy;
end

-- From https://gamedev.stackexchange.com/questions/26713/calculate-random-points-pixel-within-a-circle-image/26714
function getRandomPointInCircle(x, y, radius)
    local angle = math.random() * math.pi * 2
    radius = math.random() * radius
    local x = x + radius * math.cos(angle)
    local y = y + radius * math.sin(angle)
    return x, y
end

How to use: 

  • Start the script and then press F5 to test it.
  • Press the left mouse button and start moving the junks away.
  • Then press F5 again to reset the junks positions.

You might want to modify the hitradius/hitbox for each junk image for a better interaction with the junks. You might also want to modify the mouseRadius and the repulseSpeed (see comments for what they are meant for).

This is a pretty complex script and I don't have time to add more comments in the code for tonight, but feel free to ask what you do not understand and I'll try my best to explain it.

Edited by Citizen
typo
  • Like 2
Link to comment

Oh my god thank you so much! Works perfectly, just like I wanted it to work, thank you so much again! You helped me out alot!!

But there is one more thing I would like to do and can't figure it out how to do it. I would like to have an item behind the junks, so you have to move the junks away from the item to get it, but I don't know how can I detect if there is nothing in front of the item. I have tried it this way:

local sx, sy = guiGetScreenSize()
local junkSize = sx/3
local junkImageDistance = 50
local junkPositionX, junkPositionY = sx/2-junkSize/2, sy/2-junkSize/2
local searchingTimer = nil
local searchState = false

local junks = {} -- holds all junks images with position, rotation etc
local board = {x = junkPositionX, y = junkPositionY, width = junkSize, height = junkSize}
local item = {}
local mouseRadius = 15 -- how big the mouse's hitbox is (higher = easier to move junks)
local repulseSpeed = 1 -- how fast the junks go away from mouse's hitbox

local junkyardCollision = nil
local searchTime = 1000
local searchTimeMultiplier = 1
local minimumSearchTimeMultiplier, maximumSearchTimeMultiplier = 5, 15

local itemColor = tocolor(255,0,0,255)

function createJunkyards()
	for k,v in ipairs(junkyards) do
		junkyardCollision = createColCuboid(v[1], v[2], v[3]-0.85, v[4], v[5], v[6])
	end
end
addEventHandler("onClientResourceStart", getResourceRootElement(getThisResource()), createJunkyards)

local junkImages = {
	-- image, size, hitradius
	
	{"burgershot-bag", 128, 30},
	{"burgershot-bag", 128, 30},
	{"burgershot-bag", 128, 30},
	
	{"cup", 128, 25},
	{"cup", 128, 25},
	{"cup", 128, 25},
	{"cup", 128, 25},
	
	{"boot", 256, 75},
	
	{"bin-bag", 256, 75},
	{"bin-bag", 256, 75},
	
	{"apple", 128, 10},
	{"apple", 128, 10},
	
	{"paper", 128, 10},
	{"paper", 128, 10},
	{"paper", 128, 10},
	{"paper", 128, 10},
	{"paper", 128, 10},
	
	{"bottle", 128, 25},
	{"bottle", 128, 25},
	
	{"glass", 128, 50},
	
	{"tin-can", 128, 25},
	{"tin-can", 128, 25},
	{"tin-can", 128, 25}
}


function createJunks()
	junks = {}
	item = {}
	for k, v in ipairs(junkImages) do
		local size = v[2]
		local rot = math.random(1,360)
		local midX, midY = getRandomPointInCircle(board.x+board.width/2, board.y+board.width/2, board.width/2)
		local x, y = midX - size/2, midY - size/2 -- we want the middle of the image to be placed at that point
		table.insert(junks, {x = x, y = y, size = size, img = v[1], rot = rot, hitradius = v[3]})
	end
	local itemX, itemY = getRandomPointInCircle(board.x+board.width/2, board.y+board.width/2, board.width/3)
	local itemSize = 64
	local itemRadius = itemSize/2
	table.insert(item, {x = itemX, y = itemY, size = itemSize, radius = itemRadius})
end
showCursor(true) -- debug (add it to your command that shows the junk)
bindKey("f5", "down", createJunks) -- debug (F5 to (re)create the junk)

function renderJunk()
	dxDrawRectangle(board.x, board.y, board.width, board.height, tocolor(0, 0, 0, 150)) -- debug (junk board)
	--dxDrawCircle(board.x+board.width/2, board.y+board.width/2, board.width/4, 1, 1, 0, 360, tocolor(0, 0, 0, 255)) -- debug (spawn area - black circle)
	
	for k, item in ipairs(item) do
		itemX, itemY = item.x, item.y
		itemSize = item.size
		itemRadius = item.radius
		dxDrawCircle(itemX+itemSize/2, itemY+itemSize/2, itemRadius, 1, 1, 0, 360, tocolor(245, 140, 20, 255))
		dxDrawRectangle(itemX, itemY, itemSize, itemSize, itemColor)
	end
	
	for k, j in ipairs(junks) do
		local cx, cy = getCursorPosition()
		cx, cy = cx * sx, cy * sy -- absolute cursor position
		local imgCenterX, imgCenterY = j.x+j.size/2, j.y+j.size/2 -- center of the junk's image
		
		dxDrawImage(j.x, j.y, j.size, j.size, "junkyard/files/" ..j.img..".png", j.rot, 0, 0, tocolor(255,255,255,255), false)
		
		--dxDrawCircle(imgCenterX, imgCenterY, j.hitradius, 1, 1, 0, 360, tocolor(0, 0, 200, 255)) -- debug (junk's hitbox - blue circle)
		--dxDrawCircle(cx, cy, mouseRadius, 1, 1, 0, 360, tocolor(255, 255, 0, 255)) -- debug (mouse's hitbox)
		
		if getKeyState("mouse1") and isCircleInCircle(imgCenterX, imgCenterY, j.hitradius, cx, cy, mouseRadius) then
			-- move it away if it collides with the cursor's hitbox
			if exports["sgr_main"]:isMouseInPosition(board.x, board.y, board.width, board.height) then
				local angle = findRotation(imgCenterX, imgCenterY, cx, cy)
				local newX, newY = getPointFromDistanceRotation(imgCenterX, imgCenterY, repulseSpeed, -angle + 180)
				local offsetX, offsetY = imgCenterX - newX, imgCenterY - newY
				j.x, j.y = j.x-offsetX, j.y-offsetY
				-- debug:
				--local newX, newY = getPointFromDistanceRotation(imgCenterX, imgCenterY, 200, -angle + 180) -- debug
				--dxDrawLine(imgCenterX, imgCenterY, newX, newY, tocolor(0, 200, 0, 255)) -- debug (shows direction where the junk is going - green line)
			end
		end
		
		if not isCircleInCircle(imgCenterX, imgCenterY, j.hitradius, itemX+itemSize/2, itemY+itemSize/2, itemRadius) then
			itemColor = tocolor(0,255,0,255)
			outputChatBox("a")
		else
			itemColor = tocolor(255,0,0,255)
			outputChatBox("b")
		end
	end
end
addEventHandler("onClientRender", getRootElement(), renderJunk)

function startSearching()
	if junkyardCollision and isElement(junkyardCollision) then
		if isElementWithinColShape(localPlayer, junkyardCollision) then
			if not searchState then
				searchTimeMultiplier = math.random(minimumSearchTimeMultiplier,maximumSearchTimeMultiplier)
				local finalSearchTime = searchTime*searchTimeMultiplier
				
				showCursor(true)
				createJunks()
				addEventHandler("onClientRender", getRootElement(), renderJunk)
				searchState = true
				
				triggerServerEvent("syncSearchingFromServer", localPlayer, localPlayer, finalSearchTime)
				searchingTimer = setTimer(function()
					showCursor(false)
					triggerServerEvent("giveFoundJunkItem", localPlayer, localPlayer)
					removeEventHandler("onClientRender", getRootElement(), renderJunk)
				end,finalSearchTime,1)
			end
		else
			exports["cg_notifications"]:addNotification("Ezt a parancsot csak szeméttelepen tudod használni!", "error")
		end
	end
end
addCommandHandler("turkal", startSearching)

function isPlayerSearchingInJunkyard(player)
	if player and isElement(player) and getElementType(player) == "player" then
		if searchState == true then
			return true
		else
			return false
		end
	end
	return false
end



-----------------------------------------------------
-- From https://wiki.multitheftauto.com/wiki/DxDrawCircle (was only used for debug drawings)
function dxDrawCircle( posX, posY, radius, width, angleAmount, startAngle, stopAngle, color, postGUI )
	if ( type( posX ) ~= "number" ) or ( type( posY ) ~= "number" ) then
		return false
	end
	local function clamp( val, lower, upper )
		if ( lower > upper ) then lower, upper = upper, lower end
		return math.max( lower, math.min( upper, val ) )
	end
	radius = type( radius ) == "number" and radius or 50
	width = type( width ) == "number" and width or 5
	angleAmount = type( angleAmount ) == "number" and angleAmount or 1
	startAngle = clamp( type( startAngle ) == "number" and startAngle or 0, 0, 360 )
	stopAngle = clamp( type( stopAngle ) == "number" and stopAngle or 360, 0, 360 )
	color = color or tocolor( 255, 255, 255, 200 )
	postGUI = type( postGUI ) == "boolean" and postGUI or false
	if ( stopAngle < startAngle ) then
		local tempAngle = stopAngle
		stopAngle = startAngle
		startAngle = tempAngle
	end
	for i = startAngle, stopAngle, angleAmount do
		local startX = math.cos( math.rad( i ) ) * ( radius - width )
		local startY = math.sin( math.rad( i ) ) * ( radius - width )
		local endX = math.cos( math.rad( i ) ) * ( radius + width )
		local endY = math.sin( math.rad( i ) ) * ( radius + width )
		dxDrawLine( startX + posX, startY + posY, endX + posX, endY + posY, color, width, postGUI )
	end
	return true
end

-- From http://cgp.wikidot.com/circle-to-circle-collision-detection
function isCircleInCircle(x1, y1, r1, x2, y2, r2)
	return math.sqrt( ( x2-x1 ) * ( x2-x1 )  + ( y2-y1 ) * ( y2-y1 ) ) < ( r1 + r2 )
end

-- From https://wiki.multitheftauto.com/wiki/FindRotation
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

-- From https://wiki.multitheftauto.com/wiki/GetPointFromDistanceRotation
function getPointFromDistanceRotation(x, y, dist, angle)
    local a = math.rad(90 - angle);
    local dx = math.cos(a) * dist;
    local dy = math.sin(a) * dist;
    return x+dx, y+dy;
end

-- From https://gamedev.stackexchange.com/questions/26713/calculate-random-points-pixel-within-a-circle-image/26714
function getRandomPointInCircle(x, y, radius)
    local angle = math.random() * math.pi * 2
    radius = math.random() * radius
    local x = x + radius * math.cos(angle)
    local y = y + radius * math.sin(angle)
    return x, y
end

But for some reason the rectangle that represents the item is always green, even if there is junk on it, it never changes to red or outputs "b" and I don't really understand why. How can I detect if there is nothing in front of the item?

Link to comment
  • Moderators

Ok here you go (I've cheated the mouse radius in the result below):

e3757e88299126a1c2d7f83916471c95.gif

MP4 (HQ)

The way to do it was to set a boolean (itemDiscovered) to true before looping through the junks and set it to false if a junk's hitbox collides the item's hitbox using the previously used isCircleInCircle. After the for loop, we check if that boolean value is still true, if yes then we discovered the item. But as we are in an onClientRender, we need another boolean (itemDiscoveredAlready) to prevent it to call the discovered function for every next frames.

Source:

local sx, sy = guiGetScreenSize()
local junkSize = sx/3
local junkImageDistance = 50
local junkPositionX, junkPositionY = sx/2-junkSize/2, sy/2-junkSize/2
local searchingTimer = nil
local searchState = false

local item = nil -- no item at first
local junks = {} -- holds all junks images with position, rotation etc
local board = {x = junkPositionX, y = junkPositionY, width = junkSize, height = junkSize}
local mouseRadius = 20 -- how big the mouse's hitbox is (higher = easier to move junks)
local repulseSpeed = 5 -- how fast the junks go away from mouse's hitbox

local junkyardCollision = nil
local searchTime = 1000
local searchTimeMultiplier = 1
local minimumSearchTimeMultiplier, maximumSearchTimeMultiplier = 5, 15

function createJunkyards()
    for k,v in ipairs(junkyards) do
        junkyardCollision = createColCuboid(v[1], v[2], v[3]-0.85, v[4], v[5], v[6])
    end
end
addEventHandler("onClientResourceStart", getResourceRootElement(getThisResource()), createJunkyards)

local junkImages = {
    -- image, size, hitradius
    
    {"burgershot-bag", 128, 30},
    {"burgershot-bag", 128, 30},
    {"burgershot-bag", 128, 30},
    
    {"cup", 128, 25},
    {"cup", 128, 25},
    {"cup", 128, 25},
    {"cup", 128, 25},
    
    {"boot", 256, 75},
    
    {"bin-bag", 256, 75},
    {"bin-bag", 256, 75},
    
    {"apple", 128, 10},
    {"apple", 128, 10},
    
    {"paper", 128, 10},
    {"paper", 128, 10},
    {"paper", 128, 10},
    {"paper", 128, 10},
    {"paper", 128, 10},
    
    {"bottle", 128, 25},
    {"bottle", 128, 25},
    
    {"glass", 128, 50},
    
    {"tin-can", 128, 25},
    {"tin-can", 128, 25},
    {"tin-can", 128, 25}
}


function createJunks()
    -- creating junks
    junks = {}
    for k, v in ipairs(junkImages) do
        local size = v[2]
        local rot = math.random(1,360)
        local midX, midY = getRandomPointInCircle(board.x+board.width/2, board.y+board.width/2, board.width/2)
        local x, y = midX - size/2, midY - size/2 -- we want the middle of the image to be placed at that point
        table.insert(junks, {x = x, y = y, size = size, img = v[1], rot = rot, hitradius = v[3]})
    end
    
    -- creating the item (should be in another function imo)
    local itemSize = 64
    local itemX, itemY = getRandomPointInCircle(board.x+board.width/2, board.y+board.width/2, board.width/3)
    itemX, itemY = itemX - itemSize/2, itemY - itemSize/2 -- we want the middle of the image to be placed at that point
    local itemRadius = itemSize/2
    local color = tocolor(255, 0, 0, 255) -- full red by default
    -- no need for item to be a list of dictionaries, just a dictionary:
    item = {x = itemX, y = itemY, size = itemSize, radius = itemRadius, color = color}
    
    -- reseting that global
    itemDiscoveredAlready = false
end
showCursor(true) -- debug (add it to your command that shows the junk)
bindKey("f5", "down", createJunks) -- debug (F5 to (re)create the junk)

function renderJunk()
	-- Safety check just un case to prevent spamming errors if try to draw before calling createJunks
	if #junks == 0 or not item then return end
	
    dxDrawRectangle(board.x, board.y, board.width, board.height, tocolor(0, 0, 0, 150)) -- debug (junk board)
    --dxDrawCircle(board.x+board.width/2, board.y+board.width/2, board.width/4, 1, 1, 0, 360, tocolor(0, 0, 0, 255)) -- debug (spawn area - black circle)
    
	-- drawing item --
	local itemX, itemY = item.x, item.y
	local itemSize = item.size
	local itemRadius = item.radius
	local itemColor = item.color
	dxDrawRectangle(itemX, itemY, itemSize, itemSize, itemColor)
    
	-- drawing junks --
    local itemDiscovered = true -- will tell us after the loop if there is still a junk on it
    for k, j in ipairs(junks) do
        local cx, cy = getCursorPosition()
        cx, cy = cx * sx, cy * sy -- absolute cursor position
        local imgCenterX, imgCenterY = j.x+j.size/2, j.y+j.size/2 -- center of the junk's image
        
        dxDrawImage(j.x, j.y, j.size, j.size, "junkyard/files/" ..j.img..".png", j.rot, 0, 0, tocolor(255,255,255,255), false)
        
        -- dxDrawCircle(imgCenterX, imgCenterY, j.hitradius, 1, 1, 0, 360, tocolor(0, 0, 200, 100)) -- debug (junk's hitbox - blue circle)
        -- dxDrawCircle(cx, cy, mouseRadius, 1, 1, 0, 360, tocolor(255, 255, 0, 255)) -- debug (mouse's hitbox)
        
		-- handling item discovery
		local itemCenterX, itemCenterY = itemX + itemSize/2, itemY + itemSize/2
		-- "if the item is still considered as discovered but the current junk's hitbox collides with the item then ..."
		if itemDiscovered and isCircleInCircle(imgCenterX, imgCenterY, j.hitradius, itemCenterX, itemCenterY, itemRadius) then
			itemDiscovered = false -- "we finally consider it's not discovered"
			-- Setting it to false will also stop the code to call the isCircleInCircle above for the remaining junks
		end
		dxDrawCircle(itemCenterX, itemCenterY, itemRadius, 1, 1, 0, 360, tocolor(245, 140, 20, 255)) -- debug (item's hitbox)
		
		-- Moving junks ability:
        if getKeyState("mouse1") and isCircleInCircle(imgCenterX, imgCenterY, j.hitradius, cx, cy, mouseRadius) then
            -- move it away if it collides with the cursor's hitbox
            if exports["sgr_main"]:isMouseInPosition(board.x, board.y, board.width, board.height) then
                local angle = findRotation(imgCenterX, imgCenterY, cx, cy)
                local newX, newY = getPointFromDistanceRotation(imgCenterX, imgCenterY, repulseSpeed, -angle + 180)
                local offsetX, offsetY = imgCenterX - newX, imgCenterY - newY
                j.x, j.y = j.x-offsetX, j.y-offsetY
                -- debug:
                --local newX, newY = getPointFromDistanceRotation(imgCenterX, imgCenterY, 200, -angle + 180) -- debug
                --dxDrawLine(imgCenterX, imgCenterY, newX, newY, tocolor(0, 200, 0, 255)) -- debug (shows direction where the junk is going - green line)
            end
        end
    end
    
	-- We drew and calculated everything for that frame. Did we just discover the item ? 
    if itemDiscovered and not itemDiscoveredAlready then
        item.color = tocolor(0, 255, 0, 255) -- full green
        itemDiscoveredAlready = true -- This will prevent it to be called for every next frames
        onItemDiscovered( item ) -- call the function for when we discover the item
	end
end
addEventHandler("onClientRender", getRootElement(), renderJunk)

-- called as soon as an item is discovered
function onItemDiscovered( item )
    outputChatBox("item discovered !")
    -- do lightweight stuff here as we are still inside a onClientRender call
end

function startSearching()
    if junkyardCollision and isElement(junkyardCollision) then
        if isElementWithinColShape(localPlayer, junkyardCollision) then
            if not searchState then
                searchTimeMultiplier = math.random(minimumSearchTimeMultiplier,maximumSearchTimeMultiplier)
                local finalSearchTime = searchTime*searchTimeMultiplier
                
                showCursor(true)
                createJunks()
                addEventHandler("onClientRender", getRootElement(), renderJunk)
                searchState = true
                
                triggerServerEvent("syncSearchingFromServer", localPlayer, localPlayer, finalSearchTime)
                searchingTimer = setTimer(function()
                    showCursor(false)
                    triggerServerEvent("giveFoundJunkItem", localPlayer, localPlayer)
                    removeEventHandler("onClientRender", getRootElement(), renderJunk)
                end,finalSearchTime,1)
            end
        else
            exports["cg_notifications"]:addNotification("Ezt a parancsot csak szeméttelepen tudod használni!", "error")
        end
    end
end
addCommandHandler("turkal", startSearching)

function isPlayerSearchingInJunkyard(player)
    if player and isElement(player) and getElementType(player) == "player" then
        if searchState == true then
            return true
        else
            return false
        end
    end
    return false
end



-----------------------------------------------------
-- From https://wiki.multitheftauto.com/wiki/DxDrawCircle (was only used for debug drawings)
function dxDrawCircle( posX, posY, radius, width, angleAmount, startAngle, stopAngle, color, postGUI )
    if ( type( posX ) ~= "number" ) or ( type( posY ) ~= "number" ) then
        return false
    end
    local function clamp( val, lower, upper )
        if ( lower > upper ) then lower, upper = upper, lower end
        return math.max( lower, math.min( upper, val ) )
    end
    radius = type( radius ) == "number" and radius or 50
    width = type( width ) == "number" and width or 5
    angleAmount = type( angleAmount ) == "number" and angleAmount or 1
    startAngle = clamp( type( startAngle ) == "number" and startAngle or 0, 0, 360 )
    stopAngle = clamp( type( stopAngle ) == "number" and stopAngle or 360, 0, 360 )
    color = color or tocolor( 255, 255, 255, 200 )
    postGUI = type( postGUI ) == "boolean" and postGUI or false
    if ( stopAngle < startAngle ) then
        local tempAngle = stopAngle
        stopAngle = startAngle
        startAngle = tempAngle
    end
    for i = startAngle, stopAngle, angleAmount do
        local startX = math.cos( math.rad( i ) ) * ( radius - width )
        local startY = math.sin( math.rad( i ) ) * ( radius - width )
        local endX = math.cos( math.rad( i ) ) * ( radius + width )
        local endY = math.sin( math.rad( i ) ) * ( radius + width )
        dxDrawLine( startX + posX, startY + posY, endX + posX, endY + posY, color, width, postGUI )
    end
    return true
end

-- From http://cgp.wikidot.com/circle-to-circle-collision-detection
function isCircleInCircle(x1, y1, r1, x2, y2, r2)
    return math.sqrt( ( x2-x1 ) * ( x2-x1 )  + ( y2-y1 ) * ( y2-y1 ) ) < ( r1 + r2 )
end

-- From https://wiki.multitheftauto.com/wiki/FindRotation
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

-- From https://wiki.multitheftauto.com/wiki/GetPointFromDistanceRotation
function getPointFromDistanceRotation(x, y, dist, angle)
    local a = math.rad(90 - angle);
    local dx = math.cos(a) * dist;
    local dy = math.sin(a) * dist;
    return x+dx, y+dy;
end

-- From https://gamedev.stackexchange.com/questions/26713/calculate-random-points-pixel-within-a-circle-image/26714
function getRandomPointInCircle(x, y, radius)
    local angle = math.random() * math.pi * 2
    radius = math.random() * radius
    local x = x + radius * math.cos(angle)
    local y = y + radius * math.sin(angle)
    return x, y
end

Also note that the mouseRadius will change with resolutions as it's in pixels.

Same goes for repulseSpeed which is additionally dependent on FPS.

For better performance, you should not add too much junks nor call exported functions (at line 127) but local functions instead (consider copying and paste the function at the bottom of that script).

Edited by Citizen
Link to comment

EDIT: Here's my current script, I don't get any errors in debugscript, everything works fine except the FPS drop.

local sx, sy = guiGetScreenSize()
local junkSize = sx/3
local junkImageDistance = 50
local junkPositionX, junkPositionY = sx/2-junkSize/2, sy/2-junkSize/2

local searchState = false
local item = nil -- no item at first
local junks = {} -- holds all junks images with position, rotation etc
local board = {x = junkPositionX, y = junkPositionY, width = junkSize, height = junkSize}
local mouseRadius = 20 -- how big the mouse's hitbox is (higher = easier to move junks)
local repulseSpeed = 5 -- how fast the junks go away from mouse's hitbox
local junkyardCollision = nil

function createJunkyards()
    for k,v in ipairs(junkyards) do
        junkyardCollision = createColCuboid(v[1], v[2], v[3]-0.85, v[4], v[5], v[6])
    end
end
addEventHandler("onClientResourceStart", getResourceRootElement(getThisResource()), createJunkyards)

local junkImages = {
    -- image, size, hitradius
    
    {"burgershot-bag", 128, 30},
    {"burgershot-bag", 128, 30},
    {"burgershot-bag", 128, 30},
    
    {"cup", 128, 25},
    {"cup", 128, 25},
    {"cup", 128, 25},
    {"cup", 128, 25},
    
    {"boot", 256, 75},
    
    {"bin-bag", 256, 75},
    {"bin-bag", 256, 75},
    
    {"apple", 128, 30},
    {"apple", 128, 30},
    
    {"paper", 128, 30},
    {"paper", 128, 30},
    {"paper", 128, 30},
    {"paper", 128, 30},
    {"paper", 128, 30},
    
    {"bottle", 128, 25},
    {"bottle", 128, 25},
    
    {"glass", 128, 50},
    
    {"tin-can", 128, 25},
    {"tin-can", 128, 25},
    {"tin-can", 128, 25},
	
	
	-- duplicated amount, didn't cause lag before, but now it lags even if i delete the junks under here
	
	{"burgershot-bag", 128, 30},
    {"burgershot-bag", 128, 30},
    {"burgershot-bag", 128, 30},
    
    {"cup", 128, 25},
    {"cup", 128, 25},
    {"cup", 128, 25},
    {"cup", 128, 25},
    
    {"boot", 256, 75},
    
    {"bin-bag", 256, 75},
    {"bin-bag", 256, 75},
    
    {"apple", 128, 30},
    {"apple", 128, 30},
    
    {"paper", 128, 30},
    {"paper", 128, 30},
    {"paper", 128, 30},
    {"paper", 128, 30},
    {"paper", 128, 30},
    
    {"bottle", 128, 25},
    {"bottle", 128, 25},
    
    {"glass", 128, 50},
    
    {"tin-can", 128, 25},
    {"tin-can", 128, 25},
    {"tin-can", 128, 25}
}

function createJunks()
    -- creating junks
    junks = {}
    for k, v in ipairs(junkImages) do
        local size = v[2]
        local rot = math.random(1,360)
        local midX, midY = getRandomPointInCircle(board.x+board.width/2, board.y+board.width/2, board.width/2)
        local x, y = midX - size/2, midY - size/2 -- we want the middle of the image to be placed at that point
        table.insert(junks, {x = x, y = y, size = size, img = v[1], rot = rot, hitradius = v[3]})
    end
    
    -- creating the item (should be in another function imo)
    local itemSize = 64
    local itemX, itemY = getRandomPointInCircle(board.x+board.width/2, board.y+board.width/2, board.width/3)
    itemX, itemY = itemX - itemSize/2, itemY - itemSize/2 -- we want the middle of the image to be placed at that point
    local itemRadius = itemSize/2
    local color = tocolor(255, 0, 0, 255) -- full red by default
    -- no need for item to be a list of dictionaries, just a dictionary:
    item = {x = itemX, y = itemY, size = itemSize, radius = itemRadius, color = color}
    
    -- reseting that global
    itemDiscoveredAlready = false
end

--showCursor(true) -- debug (add it to your command that shows the junk)
--bindKey("f5", "down", createJunks) -- debug (F5 to (re)create the junk)

function renderJunk()
	-- Safety check just un case to prevent spamming errors if try to draw before calling createJunks
	if #junks == 0 or not item then return end
	
    dxDrawRectangle(board.x, board.y, board.width, board.height, tocolor(0, 0, 0, 150)) -- debug (junk board)
    --dxDrawCircle(board.x+board.width/2, board.y+board.width/2, board.width/4, 1, 1, 0, 360, tocolor(0, 0, 0, 255)) -- debug (spawn area - black circle)
    
	-- drawing item --
	local itemX, itemY = item.x, item.y
	local itemSize = item.size
	local itemRadius = item.radius
	local itemColor = item.color
	dxDrawRectangle(itemX, itemY, itemSize, itemSize, itemColor)
    
	-- drawing junks --
    local itemDiscovered = true -- will tell us after the loop if there is still a junk on it
    for k, j in ipairs(junks) do
        local cx, cy = getCursorPosition()
        cx, cy = cx * sx, cy * sy -- absolute cursor position
        local imgCenterX, imgCenterY = j.x+j.size/2, j.y+j.size/2 -- center of the junk's image
        
        dxDrawImage(j.x, j.y, j.size, j.size, "junkyard/files/" ..j.img..".png", j.rot, 0, 0, tocolor(255,255,255,255), false)
        
        --dxDrawCircle(imgCenterX, imgCenterY, j.hitradius, 1, 1, 0, 360, tocolor(0, 0, 200, 100)) -- debug (junk's hitbox - blue circle)
        --dxDrawCircle(cx, cy, mouseRadius, 1, 1, 0, 360, tocolor(255, 255, 0, 255)) -- debug (mouse's hitbox)
        
		-- handling item discovery
		local itemCenterX, itemCenterY = itemX + itemSize/2, itemY + itemSize/2
		-- "if the item is still considered as discovered but the current junk's hitbox collides with the item then ..."
		if itemDiscovered and isCircleInCircle(imgCenterX, imgCenterY, j.hitradius, itemCenterX, itemCenterY, itemRadius) then
			itemDiscovered = false -- "we finally consider it's not discovered"
			-- Setting it to false will also stop the code to call the isCircleInCircle above for the remaining junks
		end
		dxDrawCircle(itemCenterX, itemCenterY, itemRadius, 1, 1, 0, 360, tocolor(245, 140, 20, 255)) -- debug (item's hitbox)
		
		-- Moving junks ability:
        if getKeyState("mouse1") and isCircleInCircle(imgCenterX, imgCenterY, j.hitradius, cx, cy, mouseRadius) then
            -- move it away if it collides with the cursor's hitbox
            if exports["sgr_main"]:isMouseInPosition(board.x, board.y, board.width, board.height) then
                local angle = findRotation(imgCenterX, imgCenterY, cx, cy)
                local newX, newY = getPointFromDistanceRotation(imgCenterX, imgCenterY, repulseSpeed, -angle + 180)
                local offsetX, offsetY = imgCenterX - newX, imgCenterY - newY
                j.x, j.y = j.x-offsetX, j.y-offsetY
                -- debug:
                --local newX, newY = getPointFromDistanceRotation(imgCenterX, imgCenterY, 200, -angle + 180) -- debug
                --dxDrawLine(imgCenterX, imgCenterY, newX, newY, tocolor(0, 200, 0, 255)) -- debug (shows direction where the junk is going - green line)
            end
        end
    end
    
	-- We drew and calculated everything for that frame. Did we just discover the item ? 
    if itemDiscovered then
        item.color = tocolor(0, 255, 0, 255 * math.abs(getTickCount() % 1000 - 500) / 500) -- full green
		if not itemDiscoveredAlready then
			itemDiscoveredAlready = true -- This will prevent it to be called for every next frames
			setTimer(function()
				onItemDiscovered(item) -- call the function for when we discover the item
			end, 3000, 1)
		end
	end
end

-- called as soon as an item is discovered
function onItemDiscovered(item)
	searchState = false
    outputChatBox("item discovered !")
	showCursor(false)
	triggerServerEvent("giveFoundJunkItem", localPlayer, localPlayer)
	removeEventHandler("onClientRender", getRootElement(), renderJunk)
	itemDiscovered = false
	exports["sgr_main"]:syncAnimationFromServer(localPlayer, false)
    -- do lightweight stuff here as we are still inside a onClientRender call
end

function startSearching()
    if junkyardCollision and isElement(junkyardCollision) then
        if isElementWithinColShape(localPlayer, junkyardCollision) then
			if not isPedInVehicle(localPlayer) then
				if not searchState then
					searchState = true
					
					showCursor(true)
					createJunks()
					addEventHandler("onClientRender", getRootElement(), renderJunk)
					
					exports["sgr_main"]:syncAnimationFromServer(localPlayer, "BOMBER", "BOM_Plant_Loop", -1, true, false, false, false)
				end
			else
				exports["cg_notifications"]:addNotification("Járműben nem használhatod ezt a parancsot!", "error")
			end
        else
            exports["cg_notifications"]:addNotification("Ezt a parancsot csak szeméttelepen tudod használni!", "error")
        end
    end
end
addCommandHandler("turkal", startSearching)

function isPlayerSearchingInJunkyard(player)
    if player and isElement(player) and getElementType(player) == "player" then
        if searchState == true then
            return true
        else
            return false
        end
    end
    return false
end






-----------------------------------------------------
-- From https://wiki.multitheftauto.com/wiki/DxDrawCircle (was only used for debug drawings)
function dxDrawCircle( posX, posY, radius, width, angleAmount, startAngle, stopAngle, color, postGUI )
    if ( type( posX ) ~= "number" ) or ( type( posY ) ~= "number" ) then
        return false
    end
    local function clamp( val, lower, upper )
        if ( lower > upper ) then lower, upper = upper, lower end
        return math.max( lower, math.min( upper, val ) )
    end
    radius = type( radius ) == "number" and radius or 50
    width = type( width ) == "number" and width or 5
    angleAmount = type( angleAmount ) == "number" and angleAmount or 1
    startAngle = clamp( type( startAngle ) == "number" and startAngle or 0, 0, 360 )
    stopAngle = clamp( type( stopAngle ) == "number" and stopAngle or 360, 0, 360 )
    color = color or tocolor( 255, 255, 255, 200 )
    postGUI = type( postGUI ) == "boolean" and postGUI or false
    if ( stopAngle < startAngle ) then
        local tempAngle = stopAngle
        stopAngle = startAngle
        startAngle = tempAngle
    end
    for i = startAngle, stopAngle, angleAmount do
        local startX = math.cos( math.rad( i ) ) * ( radius - width )
        local startY = math.sin( math.rad( i ) ) * ( radius - width )
        local endX = math.cos( math.rad( i ) ) * ( radius + width )
        local endY = math.sin( math.rad( i ) ) * ( radius + width )
        dxDrawLine( startX + posX, startY + posY, endX + posX, endY + posY, color, width, postGUI )
    end
    return true
end
-- From http://cgp.wikidot.com/circle-to-circle-collision-detection
function isCircleInCircle(x1, y1, r1, x2, y2, r2)
    return math.sqrt( ( x2-x1 ) * ( x2-x1 )  + ( y2-y1 ) * ( y2-y1 ) ) < ( r1 + r2 )
end
-- From https://wiki.multitheftauto.com/wiki/FindRotation
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
-- From https://wiki.multitheftauto.com/wiki/GetPointFromDistanceRotation
function getPointFromDistanceRotation(x, y, dist, angle)
    local a = math.rad(90 - angle);
    local dx = math.cos(a) * dist;
    local dy = math.sin(a) * dist;
    return x+dx, y+dy;
end
-- From https://gamedev.stackexchange.com/questions/26713/calculate-random-points-pixel-within-a-circle-image/26714
function getRandomPointInCircle(x, y, radius)
    local angle = math.random() * math.pi * 2
    radius = math.random() * radius
    local x = x + radius * math.cos(angle)
    local y = y + radius * math.sin(angle)
    return x, y
end

 

Link to comment
  • Moderators

For me I already had FPS drops when you added some junks the first time.

Anyway, I improved everything I could find (didn't test, report errors if any):

local sx, sy = guiGetScreenSize()
local junkSize = sx/3
local junkImageDistance = 50
local junkPositionX, junkPositionY = sx/2-junkSize/2, sy/2-junkSize/2
local searchState = false
local item = nil -- no item at first
local junks = {} -- holds all junks images with position, rotation etc
local board = {x = junkPositionX, y = junkPositionY, width = junkSize, height = junkSize}
local mouseRadius = 20 -- how big the mouse's hitbox is (higher = easier to move junks)
local repulseSpeed = 5 -- how fast the junks go away from mouse's hitbox
local junkyardCollision = nil

function createJunkyards()
    for k,v in ipairs(junkyards) do
        junkyardCollision = createColCuboid(v[1], v[2], v[3]-0.85, v[4], v[5], v[6])
    end
end

addEventHandler("onClientResourceStart", getResourceRootElement(getThisResource()), createJunkyards)
local junkImages = {
    -- image, size, hitradius
    
    {"burgershot-bag", 128, 30},
    {"burgershot-bag", 128, 30},
    {"burgershot-bag", 128, 30},
    
    {"cup", 128, 25},
    {"cup", 128, 25},
    {"cup", 128, 25},
    {"cup", 128, 25},
    
    {"boot", 256, 75},
    
    {"bin-bag", 256, 75},
    {"bin-bag", 256, 75},
    
    {"apple", 128, 30},
    {"apple", 128, 30},
    
    {"paper", 128, 30},
    {"paper", 128, 30},
    {"paper", 128, 30},
    {"paper", 128, 30},
    {"paper", 128, 30},
    
    {"bottle", 128, 25},
    {"bottle", 128, 25},
    
    {"glass", 128, 50},
    
    {"tin-can", 128, 25},
    {"tin-can", 128, 25},
    {"tin-can", 128, 25},
	
	
	-- duplicated amount, didn't cause lag before, but now it lags even if i delete the junks under here
	
	{"burgershot-bag", 128, 30},
    {"burgershot-bag", 128, 30},
    {"burgershot-bag", 128, 30},
    
    {"cup", 128, 25},
    {"cup", 128, 25},
    {"cup", 128, 25},
    {"cup", 128, 25},
    
    {"boot", 256, 75},
    
    {"bin-bag", 256, 75},
    {"bin-bag", 256, 75},
    
    {"apple", 128, 30},
    {"apple", 128, 30},
    
    {"paper", 128, 30},
    {"paper", 128, 30},
    {"paper", 128, 30},
    {"paper", 128, 30},
    {"paper", 128, 30},
    
    {"bottle", 128, 25},
    {"bottle", 128, 25},
    
    {"glass", 128, 50},
    
    {"tin-can", 128, 25},
    {"tin-can", 128, 25},
    {"tin-can", 128, 25}
}

function createJunks()
    -- creating junks
    junks = {}
    for k, v in ipairs(junkImages) do
		local image = "junkyard/files/"..v[1].".png"
        local size = v[2]
        local rot = math.random(1,360)
        local midX, midY = getRandomPointInCircle(board.x+board.width/2, board.y+board.width/2, board.width/2)
        local x, y = midX - size/2, midY - size/2 -- we want the middle of the image to be placed at that point
        table.insert(junks, {x = x, y = y, size = size, halfSize = size/2, img = image, rot = rot, hitradius = v[3]})
    end
    
    -- creating the item (should be in another function imo)
    local itemSize = 64
    local itemX, itemY = getRandomPointInCircle(board.x+board.width/2, board.y+board.width/2, board.width/3)
    itemX, itemY = itemX - itemSize/2, itemY - itemSize/2 -- we want the middle of the image to be placed at that point
    local itemRadius = itemSize/2
    local color = tocolor(255, 0, 0, 255) -- full red by default
    -- no need for item to be a list of dictionaries, just a dictionary:
    item = {x = itemX, y = itemY, size = itemSize, halfSize = itemSize/2, radius = itemRadius, color = color}
    
    -- reseting that global
    itemDiscoveredAlready = false
end

--showCursor(true) -- debug (add it to your command that shows the junk)
--bindKey("f5", "down", createJunks) -- debug (F5 to (re)create the junk)
function renderJunk()
	-- Safety check just un case to prevent spamming errors if try to draw before calling createJunks
	if #junks == 0 or not item then return end
	
    dxDrawRectangle(board.x, board.y, board.width, board.height, tocolor(0, 0, 0, 150)) -- debug (junk board)
    --dxDrawCircle(board.x+board.width/2, board.y+board.width/2, board.width/4, 1, 1, 0, 360, tocolor(0, 0, 0, 255)) -- debug (spawn area - black circle)
    
	-- drawing item --
	local itemX, itemY = item.x, item.y
	local itemSize = item.size
	local itemHalfSize = item.halfSize
	local itemRadius = item.radius
	local itemColor = item.color
	local itemCenterX, itemCenterY = itemX + itemHalfSize, itemY + itemHalfSize
	dxDrawRectangle(itemX, itemY, itemSize, itemSize, itemColor)
    
	-- Getting the cursor position once per frame
	local cx, cy = getCursorPosition()
	cx, cy = cx * sx, cy * sy -- absolute cursor position
	
	local cursorInsideBoard = cx > board.x and cx < board.x + board.width and cy > board.y and cy < board.y + board.height
	local mouse1State = getKeyState("mouse1")
	
	-- drawing junks --
    local itemDiscovered = true -- will tell us after the loop if there is still a junk on it
    for k, j in ipairs(junks) do        
        dxDrawImage(j.x, j.y, j.size, j.size, j.img, j.rot, 0, 0, tocolor(255,255,255,255), false)
        
		-- handling item discovery
        local imgCenterX, imgCenterY = j.x+j.halfSize, j.y+j.halfSize -- center of the junk's image
		-- "if the item is still considered as discovered but the current junk's hitbox collides with the item then ..."
		if itemDiscovered and isCircleInCircle(imgCenterX, imgCenterY, j.hitradius, itemCenterX, itemCenterY, itemRadius) then
			itemDiscovered = false -- "we finally consider it's not discovered"
			-- Setting it to false will also stop the code to call the isCircleInCircle above for the remaining junks
		end
		--dxDrawCircle(itemCenterX, itemCenterY, itemRadius, 1, 1, 0, 360, tocolor(245, 140, 20, 255)) -- debug (item's hitbox)
        --dxDrawCircle(imgCenterX, imgCenterY, j.hitradius, 1, 1, 0, 360, tocolor(0, 0, 200, 100)) -- debug (junk's hitbox - blue circle)
        --dxDrawCircle(cx, cy, mouseRadius, 1, 1, 0, 360, tocolor(255, 255, 0, 255)) -- debug (mouse's hitbox)
		
		-- Moving junks ability:
        if mouse1State and cursorInsideBoard and isCircleInCircle(imgCenterX, imgCenterY, j.hitradius, cx, cy, mouseRadius) then
            -- move it away if it collides with the cursor's hitbox
			local angle = findRotation(imgCenterX, imgCenterY, cx, cy)
			local newX, newY = getPointFromDistanceRotation(imgCenterX, imgCenterY, repulseSpeed, -angle + 180)
			local offsetX, offsetY = imgCenterX - newX, imgCenterY - newY
			j.x, j.y = j.x-offsetX, j.y-offsetY
			
			-- debug:
			--local newX, newY = getPointFromDistanceRotation(imgCenterX, imgCenterY, 200, -angle + 180) -- debug
			--dxDrawLine(imgCenterX, imgCenterY, newX, newY, tocolor(0, 200, 0, 255)) -- debug (shows direction where the junk is going - green line)
        end
    end
    
	-- We drew and calculated everything for that frame. Did we just discover the item ? 
    if itemDiscovered then
        item.color = tocolor(0, 255, 0, 255 * math.abs(getTickCount() % 1000 - 500) / 500) -- full green
		if not itemDiscoveredAlready then
			itemDiscoveredAlready = true -- This will prevent it to be called for every next frames
			setTimer(function()
				onItemDiscovered(item) -- call the function for when we discover the item
			end, 3000, 1)
		end
	end
end

-- called as soon as an item is discovered
function onItemDiscovered(item)
	searchState = false
    outputChatBox("item discovered !")
	showCursor(false)
	triggerServerEvent("giveFoundJunkItem", localPlayer, localPlayer)
	removeEventHandler("onClientRender", getRootElement(), renderJunk)
	itemDiscovered = false
	exports["sgr_main"]:syncAnimationFromServer(localPlayer, false)
    -- do lightweight stuff here as we are still inside a onClientRender call
end

function startSearching()
    if junkyardCollision and isElement(junkyardCollision) then
        if isElementWithinColShape(localPlayer, junkyardCollision) then
			if not isPedInVehicle(localPlayer) then
				if not searchState then
					searchState = true
					
					showCursor(true)
					createJunks()
					addEventHandler("onClientRender", getRootElement(), renderJunk)
					
					exports["sgr_main"]:syncAnimationFromServer(localPlayer, "BOMBER", "BOM_Plant_Loop", -1, true, false, false, false)
				end
			else
				exports["cg_notifications"]:addNotification("Járműben nem használhatod ezt a parancsot!", "error")
			end
        else
            exports["cg_notifications"]:addNotification("Ezt a parancsot csak szeméttelepen tudod használni!", "error")
        end
    end
end
addCommandHandler("turkal", startSearching)

function isPlayerSearchingInJunkyard(player)
    if player and isElement(player) and getElementType(player) == "player" then
        if searchState == true then
            return true
        else
            return false
        end
    end
    return false
end

-----------------------------------------------------

-- From https://wiki.multitheftauto.com/wiki/DxDrawCircle (was only used for debug drawings)
function dxDrawCircle( posX, posY, radius, width, angleAmount, startAngle, stopAngle, color, postGUI )
    if ( type( posX ) ~= "number" ) or ( type( posY ) ~= "number" ) then
        return false
    end
    local function clamp( val, lower, upper )
        if ( lower > upper ) then lower, upper = upper, lower end
        return math.max( lower, math.min( upper, val ) )
    end
    radius = type( radius ) == "number" and radius or 50
    width = type( width ) == "number" and width or 5
    angleAmount = type( angleAmount ) == "number" and angleAmount or 1
    startAngle = clamp( type( startAngle ) == "number" and startAngle or 0, 0, 360 )
    stopAngle = clamp( type( stopAngle ) == "number" and stopAngle or 360, 0, 360 )
    color = color or tocolor( 255, 255, 255, 200 )
    postGUI = type( postGUI ) == "boolean" and postGUI or false
    if ( stopAngle < startAngle ) then
        local tempAngle = stopAngle
        stopAngle = startAngle
        startAngle = tempAngle
    end
    for i = startAngle, stopAngle, angleAmount do
        local startX = math.cos( math.rad( i ) ) * ( radius - width )
        local startY = math.sin( math.rad( i ) ) * ( radius - width )
        local endX = math.cos( math.rad( i ) ) * ( radius + width )
        local endY = math.sin( math.rad( i ) ) * ( radius + width )
        dxDrawLine( startX + posX, startY + posY, endX + posX, endY + posY, color, width, postGUI )
    end
    return true
end

-- From http://cgp.wikidot.com/circle-to-circle-collision-detection
function isCircleInCircle(x1, y1, r1, x2, y2, r2)
    return math.sqrt( ( x2-x1 ) * ( x2-x1 )  + ( y2-y1 ) * ( y2-y1 ) ) < ( r1 + r2 )
end

-- From https://wiki.multitheftauto.com/wiki/FindRotation
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

-- From https://wiki.multitheftauto.com/wiki/GetPointFromDistanceRotation
function getPointFromDistanceRotation(x, y, dist, angle)
    local a = math.rad(90 - angle);
    local dx = math.cos(a) * dist;
    local dy = math.sin(a) * dist;
    return x+dx, y+dy;
end

-- From https://gamedev.stackexchange.com/questions/26713/calculate-random-points-pixel-within-a-circle-image/26714
function getRandomPointInCircle(x, y, radius)
    local angle = math.random() * math.pi * 2
    radius = math.random() * radius
    local x = x + radius * math.cos(angle)
    local y = y + radius * math.sin(angle)
    return x, y
end

Changes:

  • No more division in the rendering part for item and junks image center (calculated at the creation and stored in the "objects" in halfSize attribute)
  • Getting the cursor position with the absolute convertion only once per frame instead of #junks per frame (cx, cy)
  • Removed your exported function and calculate if inside the board manually outside the junk loop as we have all informations we need (cursorInsideBoard)
  • Getting the mouse1 key state only once per frame instead of #junks per frame (mouse1State)
  • Moved the string concatenations for the junk's image path into createJunks function.

Also what are you trying to do at line 173 please ?

Edited by Citizen
Link to comment
  • Moderators

Alright, cool to hear it fixed the issue and that I didn't break anything by refactoring the calculations.

Feel free to remove the commented lines and debug things to make the code cleaner (the dxDrawCircle function definition can normally be removed if you don't use it anywhere else in that script).

Link to comment

I would like to ask for help with one more thing.

 

CLIENT:

function createJunks()
    -- creating junks
    junks = {}
    for k, v in ipairs(junkImages) do
		local image = "junkyard/files/"..v[1]..".png"
        local size = v[2]
        local rot = math.random(1,360)
        local midX, midY = getRandomPointInCircle(board.x+board.width/2, board.y+board.width/2, board.width/2)
        local x, y = midX - size/2, midY - size/2 -- we want the middle of the image to be placed at that point
        table.insert(junks, {x = x, y = y, size = size, halfSize = size/2, img = image, rot = rot, hitradius = v[3]})
    end
    
    -- creating the item (should be in another function imo)
    local itemSize = 64
    local itemX, itemY = getRandomPointInCircle(board.x+board.width/2, board.y+board.width/2, board.width/3)
    itemX, itemY = itemX - itemSize/2, itemY - itemSize/2 -- we want the middle of the image to be placed at that point
    local itemRadius = itemSize/2
    local color = tocolor(255, 0, 0, 255) -- full red by default
    -- no need for item to be a list of dictionaries, just a dictionary:
    item = {x = itemX, y = itemY, size = itemSize, halfSize = itemSize/2, radius = itemRadius, color = color}
    
    -- reseting that global
    itemDiscoveredAlready = false
	
	triggerServerEvent("setJunkItem", localPlayer)
	local foundItemImage = -- what to write here to get the number of the item from server side?? like items[foundItem][1] is the way I get it on server side, but there is no such thing as foundItem
end

 

SERVER:

local isItem = false
local foundItem = nil

function setJunkItem()
	--if player and isElement(player) and getElementType(player) == "player" then
		foundItem = math.random(1,#items)
		local currentChance = math.random(1,100)
		
		if currentChance <= items[foundItem][2] then
			outputChatBox("Item a táblából: " .. tostring(foundItem))
			outputChatBox("Saját Esély: " .. tostring(currentChance))
			outputChatBox(" ")
			outputChatBox("Item: " .. tostring(items[foundItem][1]))
			outputChatBox("Item Esély: " .. tostring(items[foundItem][2]))
			outputChatBox("Item Mennyiség: " .. tostring(items[foundItem][3]))
			isItem = true
		else
			isItem = false
		end
	--end
end
addEvent("setJunkItem", true)
addEventHandler("setJunkItem", getRootElement(), setJunkItem)

function giveJunkItem(player)
	if player and isElement(player) and getElementType(player) == "player" then
		if isItem then
			--if item == items[foundItem][1] then
				exports["cg_items"]:giveItem(player, items[foundItem][1], items[foundItem][3])
				outputChatBox(exports["cg_items"]:getItemName(items[foundItem][1]))
			--end
		else
			exports["cg_notifications"]:addNotification(player, "Sajnos semmit sem találtál.", "info")
			return
		end
	end
end
addEvent("giveJunkItem", true)
addEventHandler("giveJunkItem", getRootElement(), giveJunkItem)

As I wrote in a comment inside the client side script, I would like to get the item number generated in the server side script. The reason is why I want to get it is because I would like to draw the item's image instead of the red and green rectangle under the junk, but I have no idea how I can do that. Is there any way doing it? I have tried things like

-- SERVER
function setJunkItem()
    foundItem = math.random(1,#items)
    local currentChance = math.random(1,100)

    if currentChance <= items[foundItem][2] then
      outputChatBox("Item a táblából: " .. tostring(foundItem))
      outputChatBox("Saját Esély: " .. tostring(currentChance))
      outputChatBox(" ")
      outputChatBox("Item: " .. tostring(items[foundItem][1]))
      outputChatBox("Item Esély: " .. tostring(items[foundItem][2]))
      outputChatBox("Item Mennyiség: " .. tostring(items[foundItem][3]))
      isItem = true
      return items[foundItem][1]
    else
      isItem = false
      return 0
    end
end
addEvent("setJunkItem", true)
addEventHandler("setJunkItem", getRootElement(), setJunkItem)

-- CLIENT
local foundItemImage = nil

function createJunks()
    -- creating junks
    junks = {}
    for k, v in ipairs(junkImages) do
		local image = "junkyard/files/"..v[1]..".png"
        local size = v[2]
        local rot = math.random(1,360)
        local midX, midY = getRandomPointInCircle(board.x+board.width/2, board.y+board.width/2, board.width/2)
        local x, y = midX - size/2, midY - size/2 -- we want the middle of the image to be placed at that point
        table.insert(junks, {x = x, y = y, size = size, halfSize = size/2, img = image, rot = rot, hitradius = v[3]})
    end
    
    -- creating the item (should be in another function imo)
    local itemSize = 64
    local itemX, itemY = getRandomPointInCircle(board.x+board.width/2, board.y+board.width/2, board.width/3)
    itemX, itemY = itemX - itemSize/2, itemY - itemSize/2 -- we want the middle of the image to be placed at that point
    local itemRadius = itemSize/2
    local color = tocolor(255, 0, 0, 255) -- full red by default
    -- no need for item to be a list of dictionaries, just a dictionary:
    item = {x = itemX, y = itemY, size = itemSize, halfSize = itemSize/2, radius = itemRadius, color = color}
    
    -- reseting that global
    itemDiscoveredAlready = false
	
	foundItemImage = triggerServerEvent("setJunkItem", localPlayer)
end

But of course it's not working because - as MTA Wiki says - triggerServerEvent returns a bool, so I have no idea how I could do that. Could you help me out with this please?

Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...