Jump to content

IIYAMA

Moderators
  • Posts

    6,058
  • Joined

  • Last visited

  • Days Won

    208

Everything posted by IIYAMA

  1. 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.
  2. Checkout this resource script bundler tutorial:

     

  3. 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:
  4. 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.
  5. 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:
  6. 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.
  7. 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. )
  8. 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)
  9. 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)
  10. I recommend to replace most of your serverside code with (see example): https://wiki.multitheftauto.com/wiki/OnPlayerResourceStart
  11. @CastiaL For freeroam resource patch see: https://github.com/multitheftauto/mtasa-resources/pull/516
  12. 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.
  13. Have you searched in your other files for `idTablo` ?
  14. Thx for preserving the original content!
  15. 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")
  16. 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.
  17. You could try: To remove the blend mode: dxSetBlendMode Not using a custom font
  18. Here is a fix for a vulnerability for cheaters to abuse (else cheaters can create accounts for other players): addEvent("onAccountManage", true) addEventHandler("onAccountManage", root, function(typ, username, password, save_log) if source ~= client then return end -- Fix vulnerability The addAccount function returns the new created account, but can fail. Not the end of the world. addAccount(username, password) Are there any errors in this code? I can't find the variable declaration of idTablo, is this one located in another file?
  19. We might have more luck with that one.
  20. Yea true, but everything can be faked, even files. But that does not mean that you can't personalize data based on user specifications mixed with salt/private key. I will lock/close your topic, good luck with your new solution!
  21. What is wrong with fileCreate? And transfer the file data with triggerLatentClientEvent.
  22. There are a lot of things wrong with this code. I also noticed that the code contains both tabs and spaces, which mean two different users have been messing with this code (80% sure). And on top of that 50% of the code is broken... I have fixed the spaces/tabs for you, and showed you where mistakes are located. But the rest is up to you, especially since I have not clue how this is suppose to be working. function getIDFromPlayer(id) if id then local theid -- Issue: not defined idTablo = getElementsByType("id") -- Issue: Returns a table of elements, the order can vary and the index does not necessary means it's the correct it. for id, p in pairs(idTablo) do if id == p then -- Issue: Comparing integer with an element theid = i -- Issue: `i` not defined -- Issue: loop should have been broken with `break` end end return theid else return false end end function getPlayerFromID(id) if id then id = tonumber(id) -- Issue: tonumber should be before the `if id then` local theplayer -- Issue: Value is nil idTablo = getElementsByType("id") -- Issue: Returns a table of elements, the order can vary and the index does not necessary means it's the correct it. for id, p in pairs(idTablo) do if theID == id then -- Issue: theID is not defined, comparing nil with integer theplayer = p -- Issue: loop should have been broken with `break` end end return theplayer else return false end end addEventHandler("onPlayerLogin", root, function(_, hesap) setElementData(source, "loggedin", true) setElementData(source, "TuningMenuTrue", false) local hesapID = getAccountID(hesap) if idTablo[hesapID] then -- Issue: idTablo might not be defined ozelID = idTablo[hesapID] setElementID(source, ozelID) -- ozelID if from getElementsByType is an element, not an integer/string else setElementID(source, hesapID) end local oyuncu = (string.gsub(getPlayerName(source), "#%x%x%x%x%x%x", "")) local id = getElementID(source) or "-1" -- Variable `oyuncu` and `id` not being used end) addEventHandler("onResourceStart", resourceRoot, function() -- Issue: `id` is not defined if id then id = tonumber(id) -- Issue: tonumber should be before the `if id then` local theplayer -- Issue: Value is nil idTablo = getElementsByType("id") for id, p in ipairs(getElementsByType("player")) do -- Issue: neither variable `id` nor `p` are being used -- Issue: player is not defined. (shouldn't have been variable `p`?) setElementData(player, "loggedin", not isGuestAccount(getPlayerAccount(player))) setElementData(player, "TuningMenuTrue", isGuestAccount(getPlayerAccount(player))) if getPlayerAccount(player) then local hesapID = getAccountID(getPlayerAccount(player)) if idTablo[hesapID] then ozelID = idTablo[hesapID] -- Issue: Should be a local, ozelID is an element, not an id setElementID(player, ozelID) -- ozelID if from getElementsByType is an element, not an integer/string end end end end end)
  23. Most likely because of another resource. Use the AddDebugHook function on outputChatBox, to figure out which one. (this function shows you where a MTA function is called or event is triggered)
  24. If these are the only lines of code that trigger 'destroyTimers'/'destroyTrepassor', then I don't think you have to check element data on clientside. But if they are not, you could use triggerClientEvent arguments instead. But keep in mind that the clientside handler functions also has to do other things, else it doesn't make sense to have the condition isInResArea in the first place. Server triggerClientEvent ( pla, -- receiver (player) "destroyTrepassor", g_root, -- source true -- < isInResArea ) Clientside addEvent ( "destroyTrepassor", true ) addEventHandler ( "destroyTrepassor", g_loc_root, function (isInResArea) -- ... if isInResArea == true then -- ...
×
×
  • Create New...