-
Posts
822 -
Joined
-
Last visited
-
Days Won
86
Everything posted by The_GTA
-
Don't worry about being a what you call "nag". There are hurdles imposed by both the MTA system as well as the steep learning curve. Here is my fixed script portion (line 43+): -- Fn that detects when the scrollbar moves, and what it should do (set ratio of door (which door?)) local function openFL() local theVeh = getPedOccupiedVehicle(localPlayer) local value1 = guiScrollBarGetScrollPosition(flscroll) if theVeh and value1 then setVehicleDoorOpenRatio(theVeh, 2, value1 / 100) -- <- Forgot to add the part ID. **2 = front left door** end end addEventHandler("onClientGUIScroll", flscroll, openFL, false) Changes: 1) in order to detect scrolling you have to use the "onClientGUIScroll" event. the "onClientGUIChanged" event is reserved for textual changes only. 2) you mixed up the second and third parameters of setVehicleDoorOpenRatio. The second is actually the part ID, the third the ratio. You obviously were in a hurry when you forgot to add the part ID. 3) I added the "false" for the addEventHandler function call to prevent event propagation (read the wiki to understand the recommendation). The script has been tested.
-
No problem. I understand that getting used to this coding pattern takes some time. Let me lend you a hand. Did you know that you can put functions into functions in Lua? function vehModif () vehmod = guiCreateWindow(0.74, 0.22, 0.25, 0.74, "Modificacion de Vehículo", true) guiWindowSetSizable(vehmod, false) openlabel = guiCreateLabel(0.23, 0.03, 0.52, 0.04, "Aberturas", true, vehmod) guiLabelSetColor(openlabel, 51, 194, 203) guiLabelSetHorizontalAlign(openlabel, "center", false) guiLabelSetVerticalAlign(openlabel, "center") capolabel = guiCreateLabel(0.27, 0.08, 0.46, 0.02, "Capot", true, vehmod) guiLabelSetHorizontalAlign(capolabel, "center", false) guiLabelSetVerticalAlign(capolabel, "center") caposcroll = guiCreateScrollBar(0.11, 0.10, 0.79, 0.03, true, true, vehmod) fllabel = guiCreateLabel(0.27, 0.14, 0.46, 0.02, "Puerta delatera izquierda", true, vehmod) guiLabelSetHorizontalAlign(fllabel, "center", false) guiLabelSetVerticalAlign(fllabel, "center") flscroll = guiCreateScrollBar(0.11, 0.16, 0.79, 0.03, true, true, vehmod) frlabel = guiCreateLabel(0.27, 0.21, 0.46, 0.02, "Puerta delatera derecha", true, vehmod) guiLabelSetHorizontalAlign(frlabel, "center", false) guiLabelSetVerticalAlign(frlabel, "center") frscroll = guiCreateScrollBar(0.11, 0.23, 0.79, 0.03, true, true, vehmod) rllabel = guiCreateLabel(0.27, 0.27, 0.46, 0.02, "Puerta trasera izquierda", true, vehmod) guiLabelSetHorizontalAlign(rllabel, "center", false) guiLabelSetVerticalAlign(rllabel, "center") rlscroll = guiCreateScrollBar(0.11, 0.29, 0.79, 0.03, true, true, vehmod) rrlabel = guiCreateLabel(0.27, 0.34, 0.46, 0.02, "Puerta trasera derecha", true, vehmod) guiLabelSetHorizontalAlign(rrlabel, "center", false) guiLabelSetVerticalAlign(rrlabel, "center") rrscroll = guiCreateScrollBar(0.11, 0.36, 0.79, 0.03, true, true, vehmod) baullabel = guiCreateLabel(0.27, 0.41, 0.46, 0.02, "Baúl", true, vehmod) guiLabelSetHorizontalAlign(baullabel, "center", false) guiLabelSetVerticalAlign(baullabel, "center") baulscroll = guiCreateScrollBar(0.11, 0.43, 0.79, 0.03, true, true, vehmod) guiSetVisible (vehmod, false) local function openCapo(caposcroll) local theVeh = getPedOccupiedVehicle(localPlayer) local value1 = guiScrollBarGetScrollPosition(caposcroll) if theVeh and value1 then setVehicleDoorOpenRatio(theVeh, value1 / 100) end end addEventHandler("onClientGUIChanged", caposcroll, openCapo) end addEventHandler ("onClientResourceStart", getRootElement(), vehModif) function showVehModif() if (guiGetVisible(vehmod) == false) then guiSetVisible (vehmod, true) showCursor (true) else guiSetVisible (vehmod, false) showCursor (false) end end addCommandHandler("vehmod",showVehModif) bindKey ("F3", "down", showVehModif) This code works (better) because: 1) caposcroll is being created in the same function as it is being used 2) addEventHandler is being called with caposcroll as second argument after caposcroll has been created Now that we have this issue out of the way, what other issue do you have with this code?
-
Dear Moony, I think that the issue that DB 3 shows you could be related to our discussion about without a function header vs. onClientResourceStart located here. If you don't post your entire code, could you please check it if caposcroll is created inside an onClientResourceStart event handler? If so then you have to either move the addEventHandler call into the creation function or move the creation code outside of the onClientResourceStart event. I think this special code pattern of Lua and MTA could be creating quite some confusion in the MTA community. - Martin
-
There is a bug in getCameraMatrix() FoV value which prevents proper camera calculation if you are in a vehicle and accelerate. The FoV is not properly returned by that function. Please track this issue so we can get it fixed.
-
No problem, I spent some time testing the thing and it turns out I forgot to multiply with the camera Field Of View angle. So here is an ingame screenshot of it working: local function get_screen_coordinates(wx, wy, wz) local camMatArray = getElementMatrix(getCamera()); local camMat_right = camMatArray[1]; local camMat_forward = camMatArray[2]; local camMat_up = camMatArray[3]; local camMat_pos = camMatArray[4]; local camMat = Matrix(); local sW, sH = guiGetScreenSize(); local s_ratio = sH / sW; local _,_,_,_,_,_,_,fov = getCameraMatrix(); local fovRad = math.rad(fov/2); local tanfov = math.tan(fovRad); local farclip = getFarClipDistance(); camMat:setForward(Vector3(camMat_forward[1], camMat_forward[2], camMat_forward[3])); camMat:setRight(Vector3(camMat_right[1] * tanfov, camMat_right[2] * tanfov, camMat_right[3] * tanfov)); camMat:setUp(Vector3(camMat_up[1] * tanfov * s_ratio, camMat_up[2] * tanfov * s_ratio, camMat_up[3] * tanfov * s_ratio)); camMat:setPosition(Vector3(camMat_pos[1], camMat_pos[2], camMat_pos[3])); local invCamMat = camMat:inverse(); local invVec = invCamMat:transformPosition(wx, wy, wz); local function to_real_coord(val) return ( val / 2 ) + 1/2; end local ratWidth, depthDist, ratHeight = invVec.x, invVec.y, invVec.z; if (depthDist > 0) then ratWidth = ratWidth / depthDist; ratHeight = ratHeight / depthDist; if (math.abs(ratWidth) <= 1) and (math.abs(ratHeight) <= 1) then return to_real_coord(ratWidth), to_real_coord(-ratHeight), true; end end if (ratWidth == 0) and (ratHeight == 0) then return 1/2, 1/2; elseif (ratWidth == 0) then return 1/2, to_real_coord(1 / ratHeight); elseif (ratHeight == 0) then return to_real_coord(1 / ratWidth), 1/2; end local dist_to_width = math.abs(1 / ratWidth); local dist_to_height = math.abs(1 / ratHeight); local scale_dist = math.min(dist_to_width, dist_to_height); ratWidth = ratWidth * scale_dist; ratHeight = ratHeight * scale_dist; return to_real_coord(ratWidth), to_real_coord(-ratHeight), false; end createObject(1454, 0, 0, 10); addEventHandler("onClientRender", root, function() local wx, wy, wz = 0, 0, 10; local screenRelX, screenRelY, isOnScreen = get_screen_coordinates(wx, wy, wz); dxDrawText( "relX: " .. screenRelX .. ", relY: " .. screenRelY, 100, 300 ); dxDrawText( "is on screen: " .. tostring(isOnScreen), 100, 320 ); local screenWidth, screenHeight = guiGetScreenSize(); local screenX, screenY = screenRelX * screenWidth, screenRelY * screenHeight; dxDrawRectangle( screenX - 25, screenY - 25, 50, 50, 0xFFFFFFFF ); end ); Gosh I hate myself sometimes ??
-
Dear Hazardinho, here is an extended (and bugfixed, sorry about that!) version that supports getting screen coordinates as well as the border coordinates in one function. local function get_screen_coordinates(wx, wy, wz) local camMatArray = getElementMatrix(getCamera()); local camMat_right = camMatArray[1]; local camMat_forward = camMatArray[2]; local camMat_up = camMatArray[3]; local camMat_pos = camMatArray[4]; local camMat = Matrix(); local sW, sH = guiGetScreenSize(); local s_ratio = sH / sW; camMat:setForward(Vector3(camMat_forward[1], camMat_forward[2], camMat_forward[3])); camMat:setRight(Vector3(camMat_right[1], camMat_right[2], camMat_right[3])); camMat:setUp(Vector3(camMat_up[1] * s_ratio, camMat_up[2] * s_ratio, camMat_up[3] * s_ratio)); camMat:setPosition(Vector3(camMat_pos[1], camMat_pos[2], camMat_pos[3])); local invCamMat = camMat:inverse(); local invVec = invCamMat:transformPosition(wx, wy, wz); local function to_real_coord(val) return ( val / 2 ) + 1/2; end local ratWidth, depthDist, ratHeight = invVec.x, invVec.y, invVec.z; if (depthDist > 0) then ratWidth = ratWidth / depthDist; ratHeight = ratHeight / depthDist; if (math.abs(ratWidth) <= 1) and (math.abs(ratHeight) <= 1) then return to_real_coord(ratWidth), to_real_coord(-ratHeight), true; end end if (ratWidth == 0) and (ratHeight == 0) then return 1/2, 1/2; elseif (ratWidth == 0) then return 1/2, to_real_coord(1 / ratHeight); elseif (ratHeight == 0) then return to_real_coord(1 / ratWidth), 1/2; end local dist_to_width = math.abs(1 / ratWidth); local dist_to_height = math.abs(1 / ratHeight); local scale_dist = math.min(dist_to_width, dist_to_height); ratWidth = ratWidth * scale_dist; ratHeight = ratHeight * scale_dist; return to_real_coord(ratWidth), to_real_coord(-ratHeight), false; end addEventHandler("onClientRender", root, function() local wx, wy, wz = 0, 0, 0; local screenRelX, screenRelY, isOnScreen = get_screen_coordinates(wx, wy, wz); dxDrawText( "relX: " .. screenRelX .. ", relY: " .. screenRelY, 100, 300 ); dxDrawText( "is on screen: " .. tostring(isOnScreen), 100, 320 ); local screenWidth, screenHeight = guiGetScreenSize(); local screenX, screenY = screenRelX * screenWidth, screenRelY * screenHeight; dxDrawRectangle( screenX - 25, screenY - 25, 50, 50, 0xFFFFFFFF ); end ); In order to detect if an object is on the screen, simply use the third return value.
-
Since I am not that terrible at math here is my solution: local function get_screen_border_coordinates(wx, wy, wz) local camMatArray = getElementMatrix(getCamera()); local camMat_right = camMatArray[1]; local camMat_forward = camMatArray[2]; local camMat_up = camMatArray[3]; local camMat_pos = camMatArray[4]; local camMat = Matrix(); local sW, sH = guiGetScreenSize(); local s_ratio = sH / sW; camMat:setForward(Vector3(camMat_forward[1], camMat_forward[2], camMat_forward[3])); camMat:setRight(Vector3(camMat_right[1], camMat_right[2], camMat_right[3])); camMat:setUp(Vector3(camMat_up[1] * s_ratio, camMat_up[2] * s_ratio, camMat_up[3] * s_ratio)); camMat:setPosition(Vector3(camMat_pos[1], camMat_pos[2], camMat_pos[3])); local invCamMat = camMat:inverse(); local invVec = invCamMat:transformPosition(wx, wy, wz); local function to_real_coord(val) return ( val / 2 ) + 1/2; end local ratWidth, ratHeight, depthDist = invVec.x, invVec.y, invVec.z; if (ratWidth == 0) and (ratHeight == 0) then return 0, 0; elseif (ratWidth == 0) then return 0, to_real_coord(1 / ratHeight); elseif (ratHeight == 0) then return to_real_coord(1 / ratWidth), 0; end local dist_to_width = math.abs(1 / ratWidth); local dist_to_height = math.abs(1 / ratHeight); local scale_dist = math.min(dist_to_width, dist_to_height); ratWidth = ratWidth * scale_dist; ratHeight = ratHeight * scale_dist; return to_real_coord(ratWidth), to_real_coord(-ratHeight); end addEventHandler("onClientRender", root, function() local wx, wy, wz = 0, 0, 0; local screenRelX, screenRelY = get_screen_border_coordinates(wx, wy, wz); dxDrawText( "relX: " .. screenRelX .. ", relY: " .. screenRelY, 100, 300 ); local screenWidth, screenHeight = guiGetScreenSize(); local screenX, screenY = screenRelX * screenWidth, screenRelY * screenHeight; dxDrawRectangle( screenX - 25, screenY - 25, 50, 50, 0xFFFFFFFF ); end ); The mathematical background is very much related to my work at 3D software rendering, but I do not want to overcomplicate things for you.
-
Great to hear that it works now Let me try to explain to you the difference between the creation function and without a 'function' header. First, let's see the facts. The creation function is called when the "onClientResourceStart" event is triggered. Your placed elements outside of a function are executed when the script is loaded. I think you have to understand that the GUI variables ("lista", etc) are only available after the creation code has been executed. If you want to convert the tutorial script to your approach without a 'function' header, you can do the following (edited your script from the top post): local vehiclesTable = { ["Sparrow"] = 469, ["Stuntplane"] = 513, ["BF-400"] = 581, ["Freeway"] = 463, ["Speeder"] = 452, ["Jester"] = 559, ["Sabre"] = 475, ["Police Ranger"] = 599, ["Utility Van"] = 552, ["Tug"] = 583 } function showVehicleSelection() if (guiGetVisible(vehList) == false) then guiSetVisible (vehList, true) showCursor (true) else guiSetVisible (vehList, false) showCursor (false) end end addCommandHandler("veh",showVehicleSelection) function populateGridlist() -- loop through the table for name,vehicle in pairs(vehiclesTable) do -- add a new row to the gridlist local row = guiGridListAddRow(lista) -- set the text in the first column to the vehicle name guiGridListSetItemText(lista,row,1,name,false,false) -- set the text in the second column to the vehicle type guiGridListSetItemText(lista,row,2,getVehicleType(vehicle),false,false) -- set the data for gridlist slot as the vehicle id guiGridListSetItemData(lista,row,1,tostring(vehicle)) end end function createVehicleHandler(button,state) if button == "left" and state == "up" then -- get the selected item in the gridlist local row,col = guiGridListGetSelectedItem(lista) -- if something is selected if row and col and row ~= -1 and col ~= -1 then -- get the vehicle id data from the gridlist that is selected local selected = guiGridListGetItemData(lista,row,col) -- make sure the vehicle id is a number not a string selected = tonumber(selected) -- get the players position and rotation local rotz = getPedRotation(getLocalPlayer()) local x,y,z = getElementPosition(getLocalPlayer()) -- find the position directly infront of the player x = x + ( math.cos ( math.rad ( rotz+90 ) ) * 3) y = y + ( math.sin ( math.rad ( rotz+90 ) ) * 3) if selected and x and y and z then -- trigger the server triggerServerEvent("createVehicleFromGUI",getRootElement(),selected,x,y,z) -- hide the gui and the cursor guiSetVisible(windowVehicleSelection,false) showCursor(false,false) else outputChatBox("Invalid arguments.") end else -- otherwise, output a message to the player outputChatBox("Please select a vehicle.") end end end -- Create the GUI here. do vehList = guiCreateWindow(0.06, 0.33, 0.31, 0.30, "Vehiculos", true) guiWindowSetSizable(vehList, false) botonCrear = guiCreateButton(149, 260, 101, 31, "Crear", false, vehList) lista = guiCreateGridList(0.06, 0.12, 0.87, 0.69, true, vehList) guiGridListAddColumn(lista, "Coches", 0.9) guiSetVisible(vehList,false) populateGridlist() addEventHandler("onClientGUIClick", botonCrear, createVehicleHandler, false) end Both ways have no significant differences in the functionality. You could say that too many event handlers pollute the event system. It is good practice to try to implement it both ways. But I usually resort to without a 'function' header in my code. Then you have to put global functions in a specific order: if function A depends on function B, then function B has to be written earlier in the code than function A. I hope that my explanation has helped you understand more of Lua and MTA
-
Sorry for not investigating this tutorial that thoughly but you have to add the code inside the vehSelection function. Otherwise it does not work because the GUI has not been created yet. -- Put createVehicleHandler here. -- Put populateGridlist here -- Etc. function vehSelection() vehList = guiCreateWindow(0.06, 0.33, 0.31, 0.30, "Vehiculos", true) guiWindowSetSizable(vehList, false) botonCrear = guiCreateButton(149, 260, 101, 31, "Crear", false, vehList) lista = guiCreateGridList(0.06, 0.12, 0.87, 0.69, true, vehList) guiGridListAddColumn(lista, "Coches", 0.9) guiSetVisible(vehList,false) populateGridlist() addEventHandler("onClientGUIClick",botonCrear,createVehicleHandler,false) end -- Etc. Explanation: if you put code inside the onClientResourceStart event handler then the code is called after all scripts have been loaded/executed. This is useful if you want to wait until MTA has loaded all custom resource functions into _G before starting your code. Putting code into the event handler is not a requirement but a recommendation.
-
You forgot to put a comma after "client" in line 3, like this: addEvent("onPlayerReady", true); addEventHandler("onPlayerReady", root, function() triggerClientEvent(client, "onClientRecebeItemVar", root, item_ID, item_NOME, spawnedItem_ID, spawnedItem_NOME, spawnedItem_X, spawnedItem_Y, spawnedItem_Z) end) No problem. I am glad to help you!
-
Dear Moony, let me ask you a simple question because I have not yet tried this amazing tutorial (it is pretty long but looks thorough, thx for sharing!). Do you have the following script line after your createVehicleHandler function definition: addEventHandler("onClientGUIDoubleClick",lista,createVehicleHandler,false)
-
I decided to give you a sample implementation on how it could be implemented. Take a look at this code: client.Lua -- Variables that have been transfered from the server to us. local spawnedItem_ID = false; local spawnedItem_NOME = false; local spawnedItem_X = false; local spawnedItem_Y = false; local spawnedItem_Z = false; -- Function called by the timer to check if the player is in the range of an item -- For that I looped up to the highest value in the table of spawned items -- And I took the X, Y and Z values from certain tables -- And if the player is in this range, trigger the event function verificarPosition() for i = 1, table.maxn(spawnedItem_ID), 1 do local x, y, z = getElementPosition(getLocalPlayer()) if(getDistanceBetweenPoints3D(x, y, z, spawnedItem_X[i], spawnedItem_Y[i], spawnedItem_Z[i]) <= 5) then triggerEvent("onPlayerEnterItemArea", getLocalPlayer()) end end end -- Timer that repeats setTimer(verificarPosition, 500, 0) -- Event (its not been called) addEvent("onPlayerEnterItemArea", false) addEventHandler("onPlayerEnterItemArea", getRootElement(), function() outputChatBox("CALLED") end) -- Receiving server data event. addEvent("onClientReceiveSpawnedItems", true); addEventHandler("onClientReceiveSpawnedItems", root, function(_spawnedItem_ID, _spawnedItem_NOME, _spawnedItem_X, _spawnedItem_Y, _spawnedItem_Z) outputDebugString( "received spawned items on client" ); spawnedItem_ID = _spawnedItem_ID; spawnedItem_NOME = _spawnedItem_NOME; spawnedItem_X = _spawnedItem_X; spawnedItem_Y = _spawnedItem_Y; spawnedItem_Z = _spawnedItem_Z; end ); -- Tell the server that the client is ready. triggerServerEvent("onPlayerReady", root); server.Lua -- Count VARS local definirItem_Count = 0; local criarItemNoMapa_Count = 0; -- Table which stores the defined items item_ID = {} item_NOME = {} -- Table which stores spawned items on the map spawnedItem_ID = {} spawnedItem_NOME = {} spawnedItem_X = {} spawnedItem_Y = {} spawnedItem_Z = {} -- Function that defines the items function definirItem(item, nome) definirItem_Count = definirItem_Count + 1 table.insert(item_ID, definirItem_Count, item) table.insert(item_NOME, definirItem_Count, nome) end -- Function that creates the items on the map function criarItemNoMapa(item, nome, x, y, z) criarItemNoMapa_Count = criarItemNoMapa_Count + 1 createObject(item, x, y, z) table.insert(spawnedItem_ID, criarItemNoMapa_Count, item) table.insert(spawnedItem_NOME, criarItemNoMapa_Count, nome) table.insert(spawnedItem_X, criarItemNoMapa_Count, x) table.insert(spawnedItem_Y, criarItemNoMapa_Count, y) table.insert(spawnedItem_Z, criarItemNoMapa_Count, z) end -- Item definition definirItem(1853, "Chave de Fenda") definirItem(1577, "Mala Verde") definirItem(1580, "Mala Vemelha") definirItem(1579, "Mala Azul") -- TEST. criarItemNoMapa(4855, "Chave de Fenda", 0, 0, 5); addEvent("onPlayerReady", true); addEventHandler("onPlayerReady", root, function() triggerClientEvent(client, "onClientReceiveSpawnedItems", root, spawnedItem_ID, spawnedItem_NOME, spawnedItem_X, spawnedItem_Y, spawnedItem_Z ); end ); Changes to the script: 1) added missing "then" after if-condition inside verificarPosition 2) moved setTimer to after function declaration 3) added the events for sending arrays from server to client 4) fixed typo in the setTimer call (you wrote "verificarPositition" instead of "verificarPosition") 5) replaced isElementInRange with getDistanceBetweenPoints3D 6) fixed typo in the criarItemNoMapa function (uppercase X, Y, Z changed to lowercase x, y, z) 7) for-loop inside verificarPosition now starts from index 1 because by convention index-based arrays in Lua start at number 1 Is this what you want? Please compare this script with what you have fixed so far. If you have any questions then just ask. Do you want me to suggest improvements for your script?
-
Dear iPollo, let's get your script working before we get to the improvements. So let me ask some questions 1) how do you create the "spawnedItem_ID" array, plus the other related ones, on the clientside? variables that you create in serverside scripts are not automatically visible on the clientside. variable synchronization is usually done through events. 2) did you enter debugscript after starting your resource? then you might have missed the warning that the first argument to setTimer is "nil" (bad argument). So to fix your clientside script multiple changes are required. First you have to move the call to setTimer after the definition of your verificarPosition Lua function (for example into line 16). Then we have to do the following steps: 1) add a new event called "onClientReceiveSpawnedItems" as remote-event on the clientside 2) add an event handler for "onClientReceiveSpawnedItems" in which we receive the spawnedItems array 3) add a new event "onPlayerReady" as remote-event on the serverside 4) add an event handler for "onPlayerReady" in which we send the spawnedItems array to the ready client only. 5) at the end of your clientside script trigger the server event "onPlayerReady" We need this event interlock because we have to make sure that the client is initialized before he can receive the spawnedItems array. There are alternatives to this approach like initializing the array on both the clientside and the serverside so that both have their own copy.
-
After spending some time bugfixing and optimizing the implementation, the performance has been improved. It still is not as fast as a hardware GPU but you should understand why To celebrate the improvements I want to show you guys how to check with 100% accuracy whether a triangle is on the screen. We basically use the same code we do for the rendering but skip the rasterization part. Take a look at this code (test_client.Lua clientside file at the end of meta.xml): local triangle = createPlane( createVector(0, 0, 5), createVector(25, 0, 0), createVector(0, 0, 15) ); local frustum_pos = createVector(0, 0, 0); local frustum_right = createVector(0, 0, 0); local frustum_up = createVector(0, 0, 0); local frustum_front = createVector(0, 0, 0); local frustum = createViewFrustum( frustum_pos, frustum_right, frustum_up, frustum_front ); local function set_frustum_from_camera() local camMat = getElementMatrix(getCamera()); local camPos = camMat[4]; local camRight = camMat[1]; local camFront = camMat[2]; local camUp = camMat[3]; local farClip = getFarClipDistance(); local cam_frontX = camFront[1] * farClip; local cam_frontY = camFront[2] * farClip; local cam_frontZ = camFront[3] * farClip; local sW, sH = guiGetScreenSize(); local s_ratio = sW / sH; local _, _, _, _, _, _, _, fov = getCameraMatrix(); local fovRad = math.rad(fov/2); local cam_side_dist = farClip * math.tan(fovRad); local cam_up_dist = cam_side_dist / s_ratio; local cam_rightX = camRight[1] * cam_side_dist; local cam_rightY = camRight[2] * cam_side_dist; local cam_rightZ = camRight[3] * cam_side_dist; local cam_upX = camUp[1] * cam_up_dist; local cam_upY = camUp[2] * cam_up_dist; local cam_upZ = camUp[3] * cam_up_dist; frustum_pos.setX(camPos[1]); frustum_pos.setY(camPos[2]); frustum_pos.setZ(camPos[3]); frustum_right.setX(cam_rightX); frustum_right.setY(cam_rightY); frustum_right.setZ(cam_rightZ); frustum_up.setX(cam_upX); frustum_up.setY(cam_upY); frustum_up.setZ(cam_upZ); frustum_front.setX(cam_frontX); frustum_front.setY(cam_frontY); frustum_front.setZ(cam_frontZ); end addEventHandler("onClientRender", root, function() local triPos = triangle.getPos(); local triU = triangle.getU(); local triV = triangle.getV(); local vert1 = { triPos.getX(), triPos.getY(), triPos.getZ(), tocolor(255, 255, 255) }; local vert2 = { triPos.getX() + triU.getX(), triPos.getY() + triU.getY(), triPos.getZ() + triU.getZ(), tocolor(255, 255, 255) }; local vert3 = { triPos.getX() + triV.getX(), triPos.getY() + triV.getY(), triPos.getZ() + triV.getZ(), tocolor(255, 255, 255) }; dxDrawPrimitive3D("trianglelist", false, vert1, vert2, vert3); -- Check whether the triangle is on screen. set_frustum_from_camera(frustum); local inter = frustum.intersectWithTrianglePlane(triangle); dxDrawText("is intersecting: " .. tostring(not (inter == false)), 100, 300); end ); The function "set_frustum_from_camera" does apply the GTA:SA camera metrics onto our frustum object, thus accurately representing the game camera. The triangle object is located at the center of the GTA:SA map (the farming area). If you teleport there and start the resource you will see a white triangle as well as the text: "is intersecting: true". If you move the camera away from the triangle then the text will change to "is intersecting: false". Thus it is checking whether the triangle is visible through the game camera. The script is pretty slow at the moment so the framerate does stutter (Lua is to be partially blamed). But I have plans to improve the performance even futher, so please follow this thread
-
Hello Hazardinho, since I have got experience working with rotations I recommend you to use the element's matrix instead, using getElementMatrix. If you look at the wiki page you can see the examples. Explanation: The element matrix stores the rotation of the ped in a coordinate system axis. If you use setElementRotation then the euler angles are automatically converted into this matrix so this matrix is more powerful and less ambiguous than the angles. Problem of euler angles: euler angles are periodic in 360° as well as suffering from gimbal lock in MTA:SA (due to poor implementation, not wanting to going into the details). If you want to use euler angles then you probably need to normalize the angles. local function normalize_euler(angle) while (angle >= 360) do angle = angle - 360; end while (angle < 0) do angle = angle + 360; end return angle; end Then if you do the following: local rotX, rotY, rotZ = getElementRotation(ped); rotX = normalize_euler(rotX); rotY = normalize_euler(rotY); rotZ = normalize_euler(rotZ); ... you are guarranteed to get the angle in the value range including 0 to just below 360. So if you are clueless on where to start, try using the getPositionFromElementOffset Function from the wiki page using the offset x=0, y=-1, z=-0.8. - Martin
- 1 reply
-
- 1
-
I think you discovered a "flaw" in MTA's resource system: sharing of the RNG seed across multiple resources. This can be considered an exploit because a non-admin resource can corrupt the RNG of an admin resource. You could post on the bugtracker of MTA to address this, but I don't think that they want to mess around with Lua too much. So the answer is: the random seed affects every other resource running on the client or serverside (depending on where you did call math.randomseed); once you use math.randomseed it counts for the entire server or the entire client, especially across resources. It could even affect MTA native logic if it happened to use CRT random anywhere. Relevant Lua source code: https://github.com/multitheftauto/mtasa-blue/blob/master/vendor/Lua/src/lmathlib.c#L209
-
Check out this code. local last_dim = false; local function remove_world_models() outputDebugString( "removing world models" ); for n=550,19999,1 do removeWorldModel(n, 10000, 0, 0, 0); end setOcclusionsEnabled(false); end local function restore_world_models() outputDebugString( "restoring world models" ); for n=550,19999,1 do restoreWorldModel(n, 10000, 0, 0, 0); end setOcclusionsEnabled(true); end addEventHandler("onClientRender", root, function() local cur_dim = getElementDimension(localPlayer); if not (last_dim == cur_dim) then if (cur_dim == 54) then remove_world_models(); elseif (last_dim == 54) then restore_world_models(); end last_dim = cur_dim; end end ); You actually did give me quite the valuable idea for scene2res by the way. It seems reasonable to add a map type where all world models are removed, in a special dimension at least.
-
Dear mono1, did you know that the concept of "dimensions" is invented by MTA to allow for multiple MTA object realms? Dimensions do not exist in GTA:SA. Interiors is the closest matching concept. But as you know MTA currently does not have any functions to make interior 54 look the same like interior 0 (sky, world, etc). Thus your idea is not that easily possible in the way you suggested. What you can do instead is place your map into the sky (offset z+500) or create a map system that tracks dimension changes on the client-side and removes all world models only-if getElementDimension on the localPlayer is equal to 54. If you need help implementing anything just ask. - Martin
-
Dear Moony and dear _Ace, I would like to suggest moving the seeding of the pseudo RNG to start of the resource: -- Seed at start of resource, not predictable. math.randomseed(getTickCount()); function spawnPoints (player) local x,y,z,r = unpack(spawnCoords[math.random(1,#spawnCoords)]) spawnPlayer(source, x, y, z, r, validSkins[math.random(1,#validSkins)]) fadeCamera(source, true) setCameraTarget(source, source) end addEventHandler("onPlayerJoin", getRootElement(), spawnPoints) math.randomseed does affect the pseudo RNG generation in the entire resource. There is nothing you can or should do about it because random is supposed to be unpredictable. Even setting the seed counts as unpredictable. Advantage of moving the seeding outside of the "onPlayerJoin" event is that the seed is even more unpredictable. Imagine if a player did measure the time from connect to the onPlayerJoin event, does know the tick count of your server and uses that information to reliably select the skin and spawn location.
-
Dear DANFOR, welcome to our Multi Theft Auto community. I would like to recommend Callum as teacher for MTA scripting. In his thread he advertises "free scripting lessons". I did get to know Callum back in the day (2012) and he was a nice guy. Unfortunately as for me, I am busy with research so I cannot lend you my hand personally. - Martin
-
Line 10: * replace playerID with targetPlayer * replace PlayerSource with playerSource
-
Dear Moony, here is a script that fixes both overlaid sounds and the volume of distant sounds: function Sound3D(shooterPed, sound3d) local wX, wY, wZ = getPedWeaponMuzzlePosition ( shooterPed ) local sound = playSound3D (sound3d,wX, wY, wZ, false ) attachElements (sound ,shooterPed , 0, 0, 2 ) setSoundMaxDistance(sound, 50) setElementDimension(sound,getElementDimension ( shooterPed )) setElementInterior(sound,getElementInterior( shooterPed )) return sound end local weapons = { [22] = "sounds/Colt45.wav", --[id] = "sound name", } local pedSounds = {}; setWorldSoundEnabled(5,false) function onClientPlayerWeaponFire ( weapon ) if weapons[weapon] then if isElement(pedSounds[source] ) then destroyElement(pedSounds[source] ) end pedSounds[source] = Sound3D(source, weapons[weapon] ) end end addEventHandler ( "onClientPlayerWeaponFire", getRootElement(), onClientPlayerWeaponFire ) Sounds are not deleted if players leave the server. Implementing this is an exercise left for OP. P.S. Please understand that scripting is an effort that you can train. If you are having trouble with parameters like "source" or don't know about the function calls, it can be a good idea to script some random resources (exploding bottle attachment to players, weapon attachment to vehicles, etc).
-
You are creating all sounds from the gun muzzle position of the localPlayer. No wonder you hear all weapons equally as loud. I recommend you to use the source element in the onClientPlayerWeaponFire event to create the sound at. Also make sure to properly destroyElement sounds (not sure if required but just to be sure?).
-
You have misspelled detachElements, you typed detachElement without s. Does this solve your problem?