Jump to content

IIYAMA

Moderators
  • Posts

    6,042
  • Joined

  • Last visited

  • Days Won

    206

Everything posted by IIYAMA

  1. 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.
  2. 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
  3. 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)
  4. No, not through config files. Only resources might be able to do this.
  5. 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 });
  6. 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
  7. 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
  8. 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
  9. 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
  10. 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.
  11. Checkout this resource script bundler tutorial:

     

  12. 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. 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) Guard clause 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: 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: 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. Create bundle files Full script:
  13. 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.
  14. 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:
  15. 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.
  16. 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. )
  17. 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. 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). 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)
  18. 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)
  19. I recommend to replace most of your serverside code with (see example): https://wiki.multitheftauto.com/wiki/OnPlayerResourceStart
  20. @CastiaL For freeroam resource patch see: https://github.com/multitheftauto/mtasa-resources/pull/516
  21. 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.
  22. Have you searched in your other files for `idTablo` ?
  23. Thx for preserving the original content!
  24. You could use for the markers: local markers = { { -- marker data otherProp = '????', element = '<marker element>' }, { -- marker data otherProp = '????', element = '<marker element>' }, } 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")
  25. 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. 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...