Jump to content

IIYAMA

Moderators
  • Posts

    6,093
  • Joined

  • Last visited

  • Days Won

    217

Posts posted by IIYAMA

  1. 27 minutes ago, Hiding said:

    if it needs to save for example 10 players at the same time?

    It depends on the amount of players and the server.

    10 players should be fine.

    More players? It is fine to save the the current km's inside of the memory. But writing it immediately to the database will definitely be a waste of performance, that can be spend else were.

    • Like 1
  2. 4 minutes ago, Hiding said:

    and I would like to save it to db when the player leaves the server to avoid unnecessary dbExec

    For example trigger every 10 seconds a latent event. Those last <10 seconds do not really matter. You want to balance performance and accuracy.

    To reduce server load and less db crash exploit able: Save it inside of the memory and write every 10/X minutes updates to the database. + onResourceStop

     

    • Like 1
  3. 7 hours ago, Hiding said:

    but doesn't work with the onClientPlayerQuit, why?

    Multiple reasons

    • onClientPlayerQuit is for remote players only. (Other players than yourself)
      • Currently when another player leaves, the stats of all other players are being saved, except yours.
    • When you quit the game, the client code on your system will stop.
      • Not sure if there is enough time to send data on onClientResourceStop + resourceRoot. But I wouldn't rely on it.
      • Using a timer to delay sending data, makes things really impossible.

     

    Other issues:

    • onClientPlayerWasted should be attached to the localPlayer, not the root element. Else it also triggers for remote players.

    • addEventHandler("onClientPlayerWasted", localPlayer, onPlayerWasted)

       

    • The source from remote events can't be trusted, use the predefined variable client instead:
    • if not exports.login:isPlayerLoggedIn(client) then return end 
      	
      exports.login:updatePlayerStat(client, "stat", "stat", data)

     

     

     

    • Like 1
  4. 21 hours ago, RekZ said:

    I can't really find any useful information that works.

    How about screenSource?

    Here a few lines of code I copied out of my smartphone remote control app. I can vaguely remember that there were some limits I ran in to, which is why the resolution is so low. But not sure which component was affected by it -> probably the JSON format by callRemote. I probably should have used fetchRemote instead, but didn't know better at that time.

     

    Client

    myScreenSource = dxCreateScreenSource( 640 / 2, 480 / 2 )
    
    --- ...
    
    dxUpdateScreenSource( myScreenSource )
    local pixels = dxGetTexturePixels( myScreenSource )
    local jpegPixels = dxConvertPixels( pixels, 'jpeg', 20 )
    triggerServerEvent( "screen-stream", localPlayer, jpegPixels )
    
    --- ...

     

    Server

    addEvent( "screen-stream", true )
    addEventHandler( "screen-stream", root, function( jpegPixels )
    
    	callRemote( "http://127.0.0.1:3134/stream", result, base64Encode( jpegPixels ) ) -- 
    
    end )

     

    App

    http.createServer(function (req, res) {
        if (req.url == "/stream") {
            const chunks = [];
            req.on('data', chunk => chunks.push(chunk));
            req.on('end', () => {
                const data = JSON.parse(Buffer.concat(chunks).toString('utf8'));
    
                if (data != undefined && data[0] != undefined) {
                   // I used socket IO to send the screenshots to my Phone
                   // io.emit('broadcast', data[0].toString('base64'));
                   // base64 image: "data:image/jpeg;base64," + data[0].toString('base64')
                }
            })
        }
        res.end();
    }).listen(3134, "127.0.0.1");

     

    Web browser:

    const image = new Image(2560, 1440);
    // Socket IO
    socket.on("broadcast", data => {
    	image.src = "data:image/jpeg;base64," + data // Replace an image with my screen
    });

     

     

  5. image.png.fd7917510361235186b5d2705921b1b2.png

    Did you know that the definition file counts up to a whopping 1364 unique functions? While the actual value might be higher or lower (missing, unknown, class/OOP or deprecated functions). Having this amount of functions in MTA is an incredible accomplish by this community. 🥳

    Some other statistics:

    • 125 client unique events
    • 88 server unique events

     

    • Like 1
    • Thanks 1
  6. 2 hours ago, Whizz said:

    When it starts and when it finds ended.

    Downloads are not always done in a straight line. They are bound to resources. Even if the client finished downloading, a new resource can be started by the server admin.

     

    Clientside:

    Downloads are often busy when the transferbox is visible.

    https://wiki.multitheftauto.com/wiki/IsTransferBoxVisible

    According to the wiki, it is possible to forcefully show the transferbox at all times. But I doubt anybody would be willing to create a worse experience for themselves.

    https://wiki.multitheftauto.com/wiki/IsTransferBoxAlwaysVisible

     

    Serverside:

    Downloads often start at https://wiki.multitheftauto.com/wiki/OnPlayerJoin

    And end for each resource at:

    https://wiki.multitheftauto.com/wiki/OnPlayerResourceStart

     

     

     

    • Thanks 1
  7.  

    45 minutes ago, Mersad said:

    "Note: Calls may incur a performance overhead - they are not equivalent in performance to calling functions in the same resource."
    I felt that this might cause delays on the server.

    The code is ran single threaded so it doesn't really matter, it is not as if the CPU is doing nothing during that delay. Trigger events will move the execution to the next (server) frame, it is just delayed.

     

    Why that extra overhead? (for events and exports)

    • Another environment has to be accessed.
    • The data arguments/parameters have to be cloned. If tables are passed I can imagine that will be even slower.

     

    Here are some performance browser details + examples. Results will probably be different on your system.

    I expected the events to be much worse, but that doesn't really seems to be the case. Might be because exports do also return values back to the original caller. (for better or worse)

     

    function exportMe(a, b, c)
    
    end
    
    local thisResource = getThisResource()
    setTimer(function()
        for i = 1, 10000, 1 do
            call(thisResource, "exportMe", "a", "b", "c")
        end
    end, 50, 0)

    Export:
    58% > 60% CPU

     

    function exportMe(a, b, c)
    
    end
    
    addEvent("blabla", true)
    addEventHandler("blabla", resourceRoot, exportMe)
    
    setTimer(function()
        for i = 1, 10000, 1 do
            triggerEvent("blabla", resourceRoot, "a", "b", "c")
        end
    end, 50, 0)

    Event:
    62% > 64% CPU

    Context: This resourceRoot has no extra elements.

     

    function exportMe(a, b, c)
    
    end
    
    addEvent("blabla", true)
    addEventHandler("blabla", resourceRoot, exportMe)
    
    -- 400 extra children
    for i = 1, 400, 1 do
        createElement("test")
    end
    
    setTimer(function()
        for i = 1, 10000, 1 do
            triggerEvent("blabla", resourceRoot, "a", "b", "c")
        end
    end, 50, 0)

    Event:
    67 > 68% CPU

    Context: Source element with 400 extra children

     

     

     

  8. 7 minutes ago, Mersad said:

    If I were to use classic color placement (outputChatBox), it would be difficult, and editing the code would be harder. Is there a better way to achieve this? Additionally, if not, if I implement this with events, could it slow down or lag my server? What steps should I take to prevent this issue?

    I would also go for the suggestion @FernandoMTA  made, but with the syntax that works for you.

    If possible move some of the events that are triggered on the same side (client/server) to export functions.

     

    Serverside

    function customOutputChatBox  () -- export function
    	-- Add rate limit here for each player
    	-- Show message
    end

    How to create/call export functions?

     

     

    Clientside (if available)

    If you use remote access.

    function customOutputChatBox  () -- export function
    	-- Add rate limit here, protecting the server from spam
    	triggerServerEvent(...)
    end

     

     

  9. 1 hour ago, Mersad said:

    Is it possible that extensive use of events could cause the server to slow down or lag?

    It is possible, but it depends...

    There are a few ways to prevent possible lag:

    • Not trigger events with the source set to root.
    • Adding rate limits on clientside, to prevent (non cheating) players to abuse spamming events.
    • Being aware that elementdata also triggers events.

    And as TMTMTL said, some more context would be nice.

     

     

  10. Resource script bundler

    In some cases you want to intergrade one resource into another as if it is a kind of module. Currently we do not have many ways to create re-useable code.

    But in this example/snippet I will explain how to bundle resource files. You could in theory use this as a base to make your own resource importable like DGS DX Library that does. But importing is beyond the scope of this topic.

     

    Side note:

    • This tutorial is created for programmers in mind. Some basic knowledge about Lua is required.
    • Some comment lines starting with ---@, those are type annotations. Here you can find more information about those. I could have removed them, but I noticed that even without Lua Language Server some people still find them helpful.
    • Spoiler
      ---@alias <newType> <type> Reuse able type
      ---@type <type> The type of the following variable or function
      ---@see <type> Refering to a specific type 
      ---@cast <variable> <type> Replacing a type
      ---@cast <variable> -<type> Remove a specific type of a multi type. string|number|table > -number > = string|table
    • Only scripts are copied in this bundler. The other files have to be moved manually.
    • While using the bundler: you need to have the debug console open, else you will not see the error/logs.

     

    Lets get started!

    The first step is to create a serverside file called bundler.lua and add it to the meta.xml of the resource you want to bundle.

    <script src="bundler.lua" />

     

    Now we have our script, we can start with writing code for opening the meta.xml file.

    -- function ()
    
    	local metaFile = xmlLoadFile("meta.xml", true) -- true = read only
    	if not metaFile then return end
    	--- reading the meta.xml here ...
    	xmlUnloadFile(metaFile)
    
    -- end

     

    To read the meta.xml we call the function xmlNodeGetChildren, which gets all the direct children of the meta.xml root:

    local nodes = xmlNodeGetChildren(metaFile)
    for index, node in ipairs(nodes) do
    	-- Going through all nodes
    end

     

    While going through the nodes, we need to validate if each node/file is OK to be bundled.

    • We need to check if the node is a <script> node.
    • And we also do not want to bundle all files. For example, we do not want to bundle the the current bundler.lua .
      By adding the attribute bundle="true", we can selective pick the correct scripts.

    In this example I am using an IIFE (Immediately Invoked Function Expression), to be able to use a guard clauses inside of loops.

    local nodes = xmlNodeGetChildren(metaFile)
    for index, node in ipairs(nodes) do
    	(function() -- IIFE 
    		-- File validation
    		if xmlNodeGetName(node) ~= "script" or xmlNodeGetAttribute(node, "bundle") ~= "true" then return end
    		-- File is OK to be added to the bundle
    	end)()
    end

    IIFE (Immediately Invoked Function Expression)

    Spoiler
    (function () end)()
    Spoiler

    IIFE in loops: Not good for performance, but good for readability. In this case performance doesn't really matter.

    Guard clause

    Spoiler
    function test ()
    	if a == true then return end -- <<<
    end

     

     

    If the files are bundle able, we need to:

    • Get the type attribute. If none, the script is a serverside script.
    • Get and check the src (source code) attribute
      • And check if the script file actually exists on the disk.
    ---@alias scriptTypeClient "client"
    ---@alias scriptTypeServer "server"
    ---@alias scriptTypeShared "shared"
    
    --- ...
    
    (function()
    	if xmlNodeGetName(node) ~= "script" or xmlNodeGetAttribute(node, "bundle") ~= "true" then return end
    
    	---@type scriptTypeClient|scriptTypeServer|scriptTypeShared
    	local fileType = xmlNodeGetAttribute(node, "type") or "server"
    
    	local src = xmlNodeGetAttribute(node, "src")
    	if not src or src == "" or not fileExists(src) then
    		outputDebugString("File is missing, index: " .. index .. ", src:" .. tostring(src), 2)
    		return
    	end
    
    end)()
    
    --- ...

     

    If that is OK, we can open the script

    ---@alias scriptTypeClient "client"
    ---@alias scriptTypeServer "server"
    ---@alias scriptTypeShared "shared"
    
    --- ...
    
    (function()
    	if xmlNodeGetName(node) ~= "script" or xmlNodeGetAttribute(node, "bundle") ~= "true" then return end
    
    	---@type scriptTypeClient|scriptTypeServer|scriptTypeShared
    	local fileType = xmlNodeGetAttribute(node, "type") or "server"
    
    	local src = xmlNodeGetAttribute(node, "src")
    	if not src or src == "" or not fileExists(src) then
    		outputDebugString("File is missing, index: " .. index .. ", src:" .. tostring(src), 2)
    		return
    	end
    
    	-- Here we open the script
    	local scriptFile = fileOpen(src)
    	if not scriptFile then
    		outputDebugString("Unable to open file, index: " .. index .. ", src:" .. tostring(src), 2)
    		return
    	end
    
    	fileClose(scriptFile)
    end)()
    
    --- ...

     

    Example meta.xml file:

    Spoiler
    <meta>
        <info author="IIYAMA" type="script" name="MTA-Communication-Enhancement" version="2.0.0" />
    
        <min_mta_version client="1.5.4" server="1.5.4" />
    	
    	<script bundle="true" src="sync/shared/eventNameObstruct.lua" type="shared" />
    	<script bundle="true" src="sync/shared/argumentValidation.lua" type="shared" />
    	<script bundle="true" src="sync/shared/callback.lua" type="shared" />
    	<script bundle="true" src="sync/shared/sync.lua" type="shared" />
    	<script bundle="true" src="sync/shared/debug.lua" type="shared" />
    
    	<script bundle="true" src="sync/server/remoteClientAccessPoints.lua" type="server" />
    	<script bundle="true" src="sync/server/sync.lua" type="server" />
    
    	<script bundle="true" src="sync/client/remoteClientAccessPoints.lua" type="client" />
    	<script bundle="true" src="sync/client/sync.lua" type="client" />
    	
    
    	<script src="bundler/bundler.lua" type="server" />
    
    </meta>

     

    To bundle we need to read the files and save the content inside of memory. But some small tweaks have to be made.

    ---@alias scriptTypeClient "client"
    ---@alias scriptTypeServer "server"
    ---@alias scriptTypeShared "shared"
    
    ---@alias bundleType {client: string[], server: string[]}
    ---@type bundleType
    local bundleContent = {
    	client = {},
    	server = {},
    }
    
    --- ...
    
    (function()
    	--- ...
    	
    	--- Start reading here !
    	local content = "do--FILE:" .. src .. "\n" ..
    		fileRead(scriptFile, fileGetSize(scriptFile)) .. "\nend"
    	if fileType == "shared" then ---@cast fileType -scriptTypeClient, -scriptTypeServer
    		-- Bundle shared files in clientside and serverside to maintain startup order
    		bundleContent.server[#bundleContent.server + 1] = content
    		bundleContent.client[#bundleContent.client + 1] = content
    	else ---@cast fileType -scriptTypeShared
    		bundleContent[fileType][#bundleContent[fileType] + 1] = content
    	end
    
    end)()
    
    --- ...
    • Each file have to start with an additional do and ends with an extra end . This is will create a new block scope for each files, make sure that the local variables are not exposed in other files.
    • The string "\n" represents a new line.
    • The file content is saved inside of the table bundleContent.client/server. Concatenating the strings for each file might cause lag, better to do it in one go later.

     

     

    Putting it together + command handler:

    ---@alias scriptTypeClient "client"
    ---@alias scriptTypeServer "server"
    ---@alias scriptTypeShared "shared"
    
    
    addCommandHandler("bundle", function(playerSource, commandName, ...)
    	local metaFile = xmlLoadFile("meta.xml", true)
    	if not metaFile then return end
    
    	---@alias bundleType {client: string[], server: string[]}
    	---@type bundleType
    	local bundleContent = {
    		client = {},
    		server = {},
    	}
    
    	--[[
    		START META.XML file read
    	]]
    
    	local nodes = xmlNodeGetChildren(metaFile)
    	for index, node in ipairs(nodes) do
    		(function()
    			if xmlNodeGetName(node) ~= "script" or xmlNodeGetAttribute(node, "bundle") ~= "true" then return end
    
    			---@type scriptTypeClient|scriptTypeServer|scriptTypeShared
    			local fileType = xmlNodeGetAttribute(node, "type") or "server"
    
    			local src = xmlNodeGetAttribute(node, "src")
    			if not src or src == "" or not fileExists(src) then
    				outputDebugString("File is missing, index: " .. index .. ", src:" .. tostring(src), 2)
    				return
    			end
    
    			local scriptFile = fileOpen(src)
    			if not scriptFile then
    				outputDebugString("Unable to open file, index: " .. index .. ", src:" .. tostring(src), 2)
    				return
    			end
    
    			local content = "do--FILE:" .. src .. "\n" ..
    				fileRead(scriptFile, fileGetSize(scriptFile)) .. "\nend"
    			if fileType == "shared" then ---@cast fileType -scriptTypeClient, -scriptTypeServer
    				-- Bundle shared files in clientside and serverside to maintain startup order
    				bundleContent.server[#bundleContent.server + 1] = content
    				bundleContent.client[#bundleContent.client + 1] = content
    			else ---@cast fileType -scriptTypeShared
    				bundleContent[fileType][#bundleContent[fileType] + 1] = content
    			end
    			fileClose(scriptFile)
    		end)()
    	end
    	xmlUnloadFile(metaFile)
    end)

     

    Functions used to bundle clientside/serverside:

    local bundleFilePath = "example_bundle" .. "/"
    
    --[[
    * Returns 0 if there is no file to be deleted.
    * Returns 1 if the file is deleted.
    * Returns 2 if the file is unable to be deleted.
    ]]
    ---@alias fileDeleteState 0|1|2
    
    ---@param bundleContent bundleType The bundle
    ---@param typeOfFile scriptTypeServer|scriptTypeClient String 'client' or 'server
    ---@return boolean
    function createBundleFile(bundleContent, typeOfFile)
    	local file = fileCreate(bundleFilePath .. typeOfFile .. ".lua")
    	if not file then return false end
    	local bundleFile = table.concat(bundleContent[typeOfFile], "\n")
    	fileWrite(file, bundleFile)
    	fileFlush(file)
    	fileClose(file)
    	return true
    end
    
    ---@see fileDeleteState
    ---@param typeOfFile scriptTypeServer|scriptTypeClient String 'client' or 'server
    ---@return fileDeleteState state The delete state: 0, 1, 2
    function deleteBundleFile(typeOfFile)
    	if not fileExists(bundleFilePath .. typeOfFile .. ".lua") then return 0 end
    	return fileDelete(bundleFilePath .. typeOfFile .. ".lua") and 1 or 2
    end

     

    Functions used to generate meta.xml script lines.

    local bundleFilePath = "example_bundle" .. "/"
    local metaFileName = "meta_fragment"
    
    --[[
    * Returns 0 if there is no file to be deleted.
    * Returns 1 if the file is deleted.
    * Returns 2 if the file is unable to be deleted.
    ]]
    ---@alias fileDeleteState 0|1|2
    
    
    --[[
    	Creates the meta fragment file, which contains the generated meta.xml script lines
    ]]
    ---@type fun(): boolean
    function createMetaFragment()
    	local file = xmlCreateFile(bundleFilePath .. metaFileName .. ".xml", "meta")
    	if not file then return false end
    
    	local serverNode = xmlCreateChild(file, "script")
    	xmlNodeSetAttribute(serverNode, "src", bundleFilePath .. "server.lua")
    	xmlNodeSetAttribute(serverNode, "type", "server")
    
    	local clientNode = xmlCreateChild(file, "script")
    	xmlNodeSetAttribute(clientNode, "src", bundleFilePath .. "client.lua")
    	xmlNodeSetAttribute(clientNode, "type", "client")
    
    	xmlSaveFile(file)
    	xmlUnloadFile(file)
    	return true
    end
    
    --[[
    	Delete the meta fragment file
    ]]
    ---@see fileDeleteState
    ---@return fileDeleteState state The delete state: 0, 1, 2
    function deleteMetaFragment()
    	if not fileExists(bundleFilePath .. metaFileName .. ".xml") then return 0 end
    	return fileDelete(bundleFilePath .. metaFileName .. ".xml") and 1 or 2
    end

     

    And now using the functions from above to make the bundle.

    if deleteBundleFile("server") == 2 then
    	error("Unable to replace bundle file: server.lua")
    elseif createBundleFile(bundleContent, "server") then
    	outputDebugString("Created bundle file: server.lua", 4)
    end
    
    if deleteBundleFile("client") == 2 then
    	error("Unable to replace bundle file: client.lua")
    elseif createBundleFile(bundleContent, "client") then
    	outputDebugString("Created bundle file: client.lua", 4)
    end
    
    if deleteMetaFragment() == 2 then
    	error("Unable to replace bundle file: " .. metaFileName .. ".xml")
    elseif createMetaFragment() then
    	outputDebugString("Created file: " .. metaFileName .. ".xml", 4)
    end

    This happens in the following order:

    1. Delete the bundle if already exist
      • If there is a problem with deleting: stop with an error. This is the point were your text editor might be blocking the deletion of the current bundle files. You need to resolve this manually.
      • You need to have the debug console open, else you will not see the error/logs.
    2. Create bundle files

     

     

    Full script:

    Spoiler
    --[[
    	Add attribute bundle="true", to add files to the bundle.
    
    	-- Shared
    	<script bundle="true" src="shared.lua" type="shared" />
    	
    	-- Serverside files
    	<script bundle="true" src="server.lua" type="server" />
    	<script bundle="true" src="server.lua" />
    
    	-- Clientside
    	<script bundle="true" src="client.lua" type="client" />
    ]]
    
    -- The root directory of the bundles:
    local bundleFilePath = "example_bundle" .. "/"
    local metaFileName = "meta_fragment"
    
    ---@alias scriptTypeClient "client"
    ---@alias scriptTypeServer "server"
    ---@alias scriptTypeShared "shared"
    
    
    addCommandHandler("bundle", function(playerSource, commandName, ...)
    	local metaFile = xmlLoadFile("meta.xml", true)
    	if not metaFile then return end
    
    	---@alias bundleType {client: string[], server: string[]}
    	---@type bundleType
    	local bundleContent = {
    		client = {},
    		server = {},
    	}
    
    	--[[
    		START META.XML file read
    	]]
    
    	local nodes = xmlNodeGetChildren(metaFile)
    	for index, node in ipairs(nodes) do
    		(function()
    			if xmlNodeGetName(node) ~= "script" or xmlNodeGetAttribute(node, "bundle") ~= "true" then return end
    
    			---@type scriptTypeClient|scriptTypeServer|scriptTypeShared
    			local fileType = xmlNodeGetAttribute(node, "type") or "server"
    
    			local src = xmlNodeGetAttribute(node, "src")
    			if not src or src == "" or not fileExists(src) then
    				outputDebugString("File is missing, index: " .. index .. ", src:" .. tostring(src), 2)
    				return
    			end
    
    			local scriptFile = fileOpen(src)
    			if not scriptFile then
    				outputDebugString("Unable to open file, index: " .. index .. ", src:" .. tostring(src), 2)
    				return
    			end
    
    			local content = "do--FILE:" .. src .. "\n" ..
    				fileRead(scriptFile, fileGetSize(scriptFile)) .. "\nend"
    			if fileType == "shared" then ---@cast fileType -scriptTypeClient, -scriptTypeServer
    				-- Bundle shared files in clientside and serverside to maintain startup order
    				bundleContent.server[#bundleContent.server + 1] = content
    				bundleContent.client[#bundleContent.client + 1] = content
    			else ---@cast fileType -scriptTypeShared
    				bundleContent[fileType][#bundleContent[fileType] + 1] = content
    			end
    			fileClose(scriptFile)
    		end)()
    	end
    	xmlUnloadFile(metaFile)
    
    	--[[
    		END META.XML file read
    	]]
    
    	--[[
    		START FILE WRITE
    	]]
    
    	if deleteBundleFile("server") == 2 then
    		error("Unable to replace bundle file: server.lua")
    	elseif createBundleFile(bundleContent, "server") then
    		outputDebugString("Created bundle file: server.lua", 4)
    	end
    
    	if deleteBundleFile("client") == 2 then
    		error("Unable to replace bundle file: client.lua")
    	elseif createBundleFile(bundleContent, "client") then
    		outputDebugString("Created bundle file: client.lua", 4)
    	end
    
    	if deleteMetaFragment() == 2 then
    		error("Unable to replace bundle file: " .. metaFileName .. ".xml")
    	elseif createMetaFragment() then
    		outputDebugString("Created file: " .. metaFileName .. ".xml", 4)
    	end
    
    	--[[
    		END FILE WRITE
    	]]
    
    	outputDebugString("Bundle created!", 4)
    end)
    
    --[[
    * Returns 0 if there is no file to be deleted.
    * Returns 1 if the file is deleted.
    * Returns 2 if the file is unable to be deleted.
    ]]
    ---@alias fileDeleteState 0|1|2
    
    ---@param bundleContent bundleType The bundle
    ---@param typeOfFile scriptTypeServer|scriptTypeClient String 'client' or 'server
    ---@return boolean
    function createBundleFile(bundleContent, typeOfFile)
    	local file = fileCreate(bundleFilePath .. typeOfFile .. ".lua")
    	if not file then return false end
    	local bundleFile = table.concat(bundleContent[typeOfFile], "\n")
    	fileWrite(file, bundleFile)
    	fileFlush(file)
    	fileClose(file)
    	return true
    end
    
    ---@see fileDeleteState
    ---@param typeOfFile scriptTypeServer|scriptTypeClient String 'client' or 'server
    ---@return fileDeleteState state The delete state: 0, 1, 2
    function deleteBundleFile(typeOfFile)
    	if not fileExists(bundleFilePath .. typeOfFile .. ".lua") then return 0 end
    	return fileDelete(bundleFilePath .. typeOfFile .. ".lua") and 1 or 2
    end
    
    --[[
    	Creates the meta fragment file, which contains the generated meta.xml script lines
    ]]
    ---@type fun(): boolean
    function createMetaFragment()
    	local file = xmlCreateFile(bundleFilePath .. metaFileName .. ".xml", "meta")
    	if not file then return false end
    
    	local serverNode = xmlCreateChild(file, "script")
    	xmlNodeSetAttribute(serverNode, "src", bundleFilePath .. "server.lua")
    	xmlNodeSetAttribute(serverNode, "type", "server")
    
    	local clientNode = xmlCreateChild(file, "script")
    	xmlNodeSetAttribute(clientNode, "src", bundleFilePath .. "client.lua")
    	xmlNodeSetAttribute(clientNode, "type", "client")
    
    	xmlSaveFile(file)
    	xmlUnloadFile(file)
    	return true
    end
    
    --[[
    	Delete the meta fragment file
    ]]
    ---@see fileDeleteState
    ---@return fileDeleteState state The delete state: 0, 1, 2
    function deleteMetaFragment()
    	if not fileExists(bundleFilePath .. metaFileName .. ".xml") then return 0 end
    	return fileDelete(bundleFilePath .. metaFileName .. ".xml") and 1 or 2
    end

     

     

    • Thanks 1
  11. 3 hours ago, Tekken said:

    Hi, may I ask why is this a bad implementation ?

     

    I can think of a few, no actual numbers to back this up to be honest.

    • The memory access speed. While _G is also a table, the access method is done with a string instead of an integer.
      • There are a lot of items in the _G table, adding a lot of extra items:
        • Will slow down the loopup with the _G table.
        • And might slowdown the lookup time for globals in general. (no proof/numbers here to be honest)
           
      • And on top of that I am not sure if _G (runtime) lookup speed can actually be optimised by the CPU. It is not a regular table after all.
         
    • Making collision/override possible, which is very easy to do in this case. Creating bugs in your code without knowing it.

     

  12. 1 hour ago, Simon54 said:

    How to actually make this work?

    Normally you use a table.

    But if you aren't able to work with those yet. Here is a very dirty way of doing it:

    for index=1,50 do
    	guiSetVisible ( _G["button_"..index.."_img"], true )
    end

    The _G accesses global variables. (variables without the keyword local in front of them)

    ⚠️ It is not recommended to use this method more than once. It is purely to demonstrate how to do it your way. But not the way Lua intended it to be used ⚠️

     

    For working with tables, please take a look at:

     

    • Like 1
    • Thanks 1
  13. On 18/07/2024 at 23:08, Korea said:

    There is a way to 'bypass' it.

    I am aware of how that function works.

     

    But how does this related to your previous code?

    Also example code often require some more instructions/information.

     

    The thing is, I am not sure where to put your content in this case (after your second reply).

    • If you want to publish a resource, sure this is the right location. But just example code is not a resource.
    • Is it a tutorial? It is misses a lot of information/instructions, but close enough.

    What do you want this topic to be?

    You can also create wiki pages for example code.

  14. 1 hour ago, Korea said:
        screen = dxCreateScreenSource(sx, sy) -- your choice

    The dxCreateScreenSource function creates the screenSource element. (it can fail if there is not enough video memory, which results in returning the value false instead)

    Important:
    Elements can't be directly saved inside variable, a variable can only point to an element (see userdata). Thus overriding the value of variable screen, does not delete the previous element. Which leads to a memory leak.

    Recommendation:
    The screenSource texture element should only be created ones at the top of your resource.

    Or you can do something like this:

    if not screen then screen = dxCreateScreenSource(sx, sy) end -- of not exist, attempt to create the screenSource texture
    if screen then dxUpdateScreenSource(screen, true) end -- if exist, update the screenSource

     

    @Korea

    (If you want to me to update your current example, feel free to ask below. Also this topic might be better of at scripting > tutorials, but it is your choice. )

    • Like 1
  15.  

    12 hours ago, DarkStalker30 said:

    1. Can I'll write to your's PM for some questions in future? Looks like you are advanced user and can help with many problems 😅

    You can, but it is sometimes better to ask your main questions here. And if nobody knows the answer, you can always send me a message to a specific topic. There are more people out there that could have provided this answer.

     

    12 hours ago, DarkStalker30 said:

    2. Nothing go bad if I use "root" in client events? Because on wiki was written that root should not use, only in specific moments.

    The variable root is predefined variable, containing the root element pointer.

    https://wiki.multitheftauto.com/wiki/Element_tree

    Which is the great parent of parents ^^. Take a look at the schematic (on the wiki).

    Tre.png

     

    Events work with a kind parent to child relation. Triggering an event on an element with a lot of children can increase CPU usage significant (execution time more or less 0.5% for each child extra, based on a sample created on my specific system).

    • So it is to reduce the CPU usage.
    • But also making sure you do not clash with other resources. Because an event is a global interaction, a resource can trigger an eventHandler for another resource.

    It is recommended to trigger an event with the resourceRoot, a specific element of that resource or a player.

     

    The resourceRoot is a predefined variable that is the root element for a specific resource (See purple nodes in the schematic). 

    • There is a resourceRoot for every resource and each of them is unique.
    • [For each resource]: The resourceRoot of clientside is linked to the resourceRoot of serverside. They are considered more or less the same (In technical terms that is not the case, but you will not notice it).

    The benefit of using resourceRoot is that the eventHandlers that listen to resourceRoot, will only trigger for the same resourceRoot or (by default) their children.

     

    The following example shows how a resource communicates from serverside to clientside. And the communication is scoped with the resource itself.

    • This does not mean that other resources can't listen to the "onGreeting" event, but the eventHandler will not trigger for other resources that run the exact same code.
    -- Server
    
    -- (playerSource is not yet defined)
    triggerClientEvent ( playerSource, "onGreeting", resourceRoot )

     

    -- Client
    addEvent( "onGreeting", true )
    addEventHandler( "onGreeting", resourceRoot, function () end ) -- Default: resourceRoot + children
    
    addEvent( "onGreeting", true )
    addEventHandler( "onGreeting", resourceRoot, function () end, true ) -- resourceRoot + children
    
    addEvent( "onGreeting", true )
    addEventHandler( "onGreeting", resourceRoot, function () end, false ) -- only resourceRoot

     

    See also:

    local thisElement = resourceRoot 
    
    addEvent( "onGreeting", true )
    addEventHandler( "onGreeting", thisElement, function () end ) -- thisElement should have the same value >
    
    -- Triggering an event on the same side(server or client)
    triggerEvent("onGreeting", thisElement) -- > as thisElement or a child of it.

     

    If using root on an eventHandler, it will trigger for all elements, since this is the great parent of parents... All resources will be able to trigger this event, no matter which element they use.

    addEvent( "onGreeting", true )
    addEventHandler( "onGreeting", root, function () end )

     

    Is it possible to trigger the following eventHandler from another resource?

    addEvent( "onGreeting", true )
    addEventHandler( "onGreeting", resourceRoot, function () end )

    Technically yes, elements from another resource are all part of the element tree. So as long as you use the right element, it is possible.

     

     

    (normally I do not explain this much)

     

    • Like 1
  16. 25 minutes ago, [FOTL]Aphex said:

    Any ideas what I'm doing wrong?

     

    Not exactly haha, but I can give you some (bad) advice. How about using some brute force for a starter? (First make it, then optimise it)

    function adjustVehicle()
    	local veh = getPedOccupiedVehicle(localPlayer)
    	if not veh then return end
    	local rx, ry = getElementRotation(veh)
    	if getKeyState("d") then
    		setControlState(localPlayer, "accelerate", true)
    		setElementRotation(veh, rx, ry, 90)
    	elseif getKeyState("a") then
    		setControlState(localPlayer, "accelerate", true)
    		setElementRotation(veh, rx, ry, -90)
    	else
    		setControlState(localPlayer, "accelerate", false)
    	end
    end
    
    addEventHandler("onClientPreRender", root, adjustVehicle)

     

     

     

  17. 21 hours ago, CastiaL said:

    how can I prevent this?

     

    I quickly looked at the code and there seems to be a vulnerability here:

    fr_server.lua

    addEvent('onServerCall', true)
    addEventHandler('onServerCall', resourceRoot,
    	function(fnName, ...)
    		source = client		-- Some called functions require 'source' to be set to the triggering client
    		local fnInfo = g_RPCFunctions[fnName]
    
    		-- Custom check made to intercept the jetpack on custom gravity
    		if fnInfo and type(fnInfo) ~= "boolean" and tostring(fnInfo.option) == "jetpack" then
    			if tonumber(("%.3f"):format(getPedGravity(source))) ~= 0.008 then
    				errMsg("* You may use jetpack only if the gravity is set to 0.008", source)
    				return
    			end
    		end
    
    		if fnInfo and ((type(fnInfo) == 'boolean' and fnInfo) or (type(fnInfo) == 'table' and getOption(fnInfo.option))) then
    			local fn = _G
    			for i,pathpart in ipairs(fnName:split('.')) do
    				fn = fn[pathpart]
    			end
    			----------------------------
    			fn(...) -- LOCATED HERE
    			----------------------------
    		elseif type(fnInfo) == 'table' then
    			errMsg(fnInfo.descr .. ' is not allowed', source)
    		end
    	end
    )

    The given arguments for any of the functions below are not validated.

    Spoiler
    g_RPCFunctions = {
    	addPedClothes = { option = 'clothes', descr = 'Modifying clothes' },
    	addVehicleUpgrade = { option = 'upgrades', descr = 'Adding/removing upgrades' },
    	fadeVehiclePassengersCamera = true,
    	fixVehicle = { option = 'repair', descr = 'Repairing vehicles' },
    	giveMeVehicles = { option = 'createvehicle', descr = 'Creating vehicles' },
    	giveMeWeapon = { option = 'weapons.enabled', descr = 'Getting weapons' },
    	removePedClothes = { option = 'clothes', descr = 'Modifying clothes' },
    	removePedFromVehicle = true,
    	removeVehicleUpgrade = { option = 'upgrades', descr = 'Adding/removing upgrades' },
    	setElementAlpha = { option = 'alpha', descr = 'Changing your alpha' },
    	setElementInterior = true,
    	setMySkin = { option = 'setskin', descr = 'Setting skin' },
    	setPedAnimation = { option = 'anim', descr = 'Setting an animation' },
    	setPedFightingStyle = { option = 'setstyle', descr = 'Setting fighting style' },
    	setPedGravity = { option = 'gravity.enabled', descr = 'Setting gravity' },
    	setPedStat = { option = 'stats', descr = 'Changing stats' },
    	setPedWearingJetpack = { option = 'jetpack', descr = 'Adding/removing a jetpack' },
    	setVehicleColor = true,
    	setVehicleHeadLightColor = true,
    	setVehicleOverrideLights = { option = 'lights', descr = 'Forcing lights' },
    	setVehiclePaintjob = { option = 'paintjob', descr = 'Applying paintjobs' },
    	warpMeIntoVehicle = true,
    }

     

     

  18. You could use for the markers:

    local markers = {
    	{	-- marker data
    		otherProp = '????',
    		element = '<marker element>'
    	},
    	{	-- marker data
    		otherProp = '????',
    		element = '<marker element>'
    	},
    }

     

    20 hours ago, [FOTL]Aphex said:

    With a `for` loop checking/dictating which markers are visible to which players?

    Loops are not needed if you use the `player` as key.

     

    And as for the players and their data:

    ---@type {[userdata]: {[string]: any}}
    local playerData = {}
    
    --- Give a player their own table/data
    ---@param player userdata
    function initPlayerData(player)
    	if playerData[player] then return end
    	playerData[player] = {} -- < sub table
    end
    
    ---@param player userdata
    ---@param key string
    ---@param data any
    function setPlayerData(player, key, data)
    	if not playerData[player] then return end
    	playerData[player][key] = data
    end
    
    ---@param player userdata
    ---@param key string
    ---@return unknown|nil
    function getPlayerData(player, key)
    	if not playerData[player] then return end
    	return playerData[player][key]
    end

    Usage example:

    --- SET
    setPlayerData(player, "nextMarker", marker)
    
    --- GET
    local marker = getPlayerData(player, "nextMarker")

     

     

     

    • Like 1
  19. 1 hour ago, [FOTL]Aphex said:

    So is there any way to only see and affect the markers created on your own client?

    Clientside is basically that.

    Every client=player runs a copy of the same code on their own pc and the (variable) data is not shared.

     

    Going for clientside?

    As long as you do not mind the following, go for it!

    • Cheater can take advantage of this. (for example being able to win in an instance)
    • It is harder to maintain round management/win/lose etc.

    Note: Remote players(other players than yourself) can also trigger some events in your copy of the code. So adding checks like this are important:

    if(hitElement == localPlayer) then

     

    Going for serverside?

    • Using tables is an essential for the serverside version of your current code. ⚠️ If you want to create bug free code on serverside, it is one of the things you have to master first. Tables are used to give every player their own piece of data, for example to keep track of the race/checkpoint progress for every player separately.

     

    1 hour ago, [FOTL]Aphex said:

    but it fried my brain so I figured clientside would be simple

    Simple does not always equal better unfortunately. Give serverside a try if you are ready for it, but shouldn't be ignored completely. You are playing a multiplayer game after all.

×
×
  • Create New...