Jump to content

Addlibs

Members
  • Posts

    1,060
  • Joined

  • Last visited

  • Days Won

    9

Posts posted by Addlibs

  1. What do you mean by "to JavaScript"? Where is it running? Are you talking about a js script on a website?

    If so, you can send and receive data to and from a Js script using HTTP requests to the relevant MTA server web interface (which uses HTTP Basic authentication over unencrypted HTTP). A JavaScript webserver (such as node.js) can do this too.

    Note, if you intend to connect to the HTTP web interface for your server, the best practice is to only do HTTP connections internally on the same network, and block the port for any external IPs - instead, route the external traffic through a gateway reverse proxy like nginx or Apache with HTTPs enabled. Otherwise you risk exposing privileged login credentials.

    MTA scripts can likewise send and receive data from JS websites using HTTP requests fetchRemote and callRemote. Data can be serialized using toJSON and receive them using fromJSON. This will not work for userdata types, including all elements. You should send names, identifiers or any other data you may need for processing any such element, as they will not be obtainable through JS,

    ref and unref functions exist and can allow you to send numerical references to the elements that can be de-referenced at another point in time, but in general, it is better to assign element IDs (setElementID and getElementID) to unique values and use those to send references to these elements. You can look up (de-reference) the element using getElementByID.

  2. 1 hour ago, Hydra said:

    Shouldn't it be?
     

    if key == "lctrl" and key == "mouse1" then
       cancelEvent()
    end

     

    No.

    Since "lctrl" and "mouse1" are not equal to each other, If key is "lctrl" then by definition it cannot be "mouse1", therefore this conditional always evaluates false, and cancelEvent is never called.

    If you want to block either lctrl or mouse1, keep the logical connective as or.

    addEventHandler("onClientKey", root,
      function(key, pressState)
        if key == "lctrl" or key == "mouse1" then -- block the use of either key
          cancelEvent(true)
        end
      end
    )

    If you want to block both from being used at the same time, you instead have to use an internal state to remember whether the other key is pressed. For example, have a variable (bool) remember whether lctrl is currently pressed or not, and when key == "mouse1" you can test

    local lctrlIsPressed = -- assigned at previous invocation of onClientKey
    
    addEventHandler("onClientKey", root,
      function(key, pressState)
        if key == "lctrl" then
          lctrlIsPressed = pressState -- remember the pressState of lctrl
        elseif key == "mouse1" and pressState and lctrlIsPressed == true then -- only if mouse1 was now pressed and lctrl is already pressed
          cancelEvent(true)
        end
      end
    )

    Note the example above is only half correct; it prevents the detection of pressing mouse1 while lctrl is pressed, but not the opposite; if one clicks and holds mouse1 and only then presses lctrl, this will not be prevented from detection by the game.

    • Like 1
  3. The best way to sync fire is the same way you'd sync most things, and there are two options: the server creates fire and distributes information about it to clients, or clients create fire and inform the server, which then broadcasts the information to the other clients.

    The second option is basically a slightly expanded version of the first, so I'll outline how the first could work:

    • Server runs a resource responsible for synchronizing fires between players.
    • This resource has an exported function or custom event
      • Security point: Don't allow remote trigger for custom events unless necessary, keep it server-triggered to prevent untrusted clients from being able to triggerServerEvent and spam the server with unauthorized fire; or, allow remote trigger but add logic to prevent unruly clients from being able to spawn fire where ever they want.
    • When the exported function is called, or the custom event triggered, the server adds to its table of existing fires certain data about the fire, including position, interior, dimension, size, maximum duration and timestamp or tickcount when spawned, and triggers a client event for all players: onReceiveSyncedFireData (or whatever you choose to name it) and sends this data through
    • Each client handles this event, and calls createFire on the client, along with timers to delete the fire when the current timestamp/tickcount exceeds the spawn timestamp/tickcount plus duration
      • If a tickcount is used, it's best if the client at this point determines their tickcount offset from the server's, and uses its own tickcount plus/minus server offset for further calculations.
    • A new client joining should request existing fires by triggering on the server an event onRequestSyncedFireData (or whatever you choose to name it)
      • The server handles this event by triggering on the new client the previously mentioned receive event for each fire in the server's fires table.
        • This client handles the receive events the exactly the same as if the fires were newly spawned.
    • If your scripts move fires around, you may add events that allow clients to make updates, in a similar way to the above but information flowing from the client to the server, and then broadcast down to the other clients
      • Security point: ideally check that only the element syncer is allowed to make changes to a fire, to prevent cheaters from tampering with fires they're nowhere near
    • Like 1
  4. for theKey,peds in ipairs(ped) do
      if ped and isElement(peds) and isElementStreamedIn(peds) then
        local Zx,Zy,Zz = getElementPosition(peds)
        zedSound = playSound3D(zombiesounds[math.random(1,#zombiesounds)], Zx, Zy, Zz, false)

    zedSound variable is overwritten for every ped. This means you're creating new sounds but discarding the pointer/reference/element handle for the old one. The old one will thus remain playing and you have no way* to delete it. What you need instead is a table of sounds you've spawned, and when you call stopSound, you should call it once per sound element in that table. This should stop all the sounds you've created.

    Example:

    -- when creating
    spawnedSounds = {}
    for theKey,peds in ipairs(ped) do
      if ped and isElement(peds) and isElementStreamedIn(peds) then
        local Zx,Zy,Zz = getElementPosition(peds)
        zedSound = playSound3D(zombiesounds[math.random(1,#zombiesounds)], Zx, Zy, Zz, false)
        -- do whatever with zedSound
        spawnedSounds[ped] = zedSound -- store the zedSound into a table for later when calling stopSound
    
    
        -- when stopping
        for theKey,theSound in pairs(spawnedSounds) do -- go through each sound in the table (note the use of pairs is 
          -- essential if the keys of the table are not numerical and sequential: in this case they are elements so pairs, is needed)
          
          -- (also NB: some performance comparisons point out pairs is faster than ipairs even with sequential numeric tables
          --  so you can just blindly always use pairs instead of ipairs without performance impact)
          stopSound(theSound) -- stop the sound that is stored as the value of a key=value pair in each row of the table
        end

    * You can still recover the pointer/reference/element handle though functions like getElementsByType for sound type but its going to be much harder to identify which one you want.

    • Thanks 1
  5.   

    Don't generate a new passwordHash on login. passwordHash should be used when registering, and only once; the result should be saved in the database. When logging in, take that hash (which includes a salt) and use passwordVerify of the input password against the saved hash. What this does internally is take the salt from the hash and similarly hash the input password, using the salt from the database hash, to come up with the exact same resultant hash if the provided plain text password is correct, or a different hash if it isn't (the result of the comparison of these hashes is the boolean return from passwordVerify).

    -- ...
                local passHashFromDB = result[1]["password"]
                local passVerified = passwordVerify(pass, passHashFromDB)
                if passVerified then
                    print("Sikerült")
                else
                    print("Nem Sikerült")
                end
    -- ...

    Also, don't trust the client to hash the password! The server should generate the hash in the server-side handling of the registration event:

    -- client side
    function Reg()
        local Empty = false
    
        if Data[2][1] == "" or Data[2][2] == "" then
            Empty = true
            LogAlert("EmptyRectangle")
        end
    
        if not Empty then
            if string.len(Data[2][1]) > 3 then
                if string.len(Data[2][2]) > 3 then
                    if Data[2][2] == Data[2][3] then
                        triggerServerEvent("attemptReg", resourceRoot, Data[2][1], Data[2][2])
                    else
                        LogAlert("NotMatch")
                    end
                else
                    LogAlert("ToShortPass")
                end
            else
                LogAlert("ToShortUS")
            end
        end
    end
    
    -- server side
    addEvent("attemptReg", true)
    addEventHandler("attemptReg", resourceRoot, function(username, pass)
        local serial = getPlayerSerial(client)
        local dq = dbQuery(db, "SELECT * FROM accounts WHERE serial=?", serial)
        local result = dbPoll(dq, 250)
    
        if result and #result > 0 then
          outputChatBox("Felhasználó már létezik.", client)
        else
          if (not result) then
            -- abort function early to avoid inserting duplicate accounts!!
            outputDebugString("DATABASE LOOKUP FAILED: Aborting registration for " .. username .. "/" .. serial)
            return
          end
          dbExec(db, "INSERT INTO accounts (username, password, serial) VALUES (?, ?, ?)", username, passwordHash(pass, "bcrypt", {}), serial)
          outputChatBox("Sikerült", client)
        end
      end)

    I added an extra early-abort code, but this should not be used in production! Change this to use callbacks. Your existing code, if it failed to retrieve results within 250ms, would create duplicate accounts, because result would be nil (i.e. result not ready) so the "Felhasználó már létezik." output would not be run.

    Also, there is no checking for duplicate account names right now (only duplicate serial check); depending on how you set up your DB tables, this may still result in duplicate account names, or the DB will reject the insertion but the user will be told registration was successful. You may want to fix that.

  6. You should not be hashing the password on the client side:

    function Login()
        if Data[1][1] ~= "" and Data[1][2] ~= "" then
            local hashedPass = passwordHash(Data[1][2], "bcrypt",{}) -- this is wrong
    
            triggerServerEvent("attemptLogin", resourceRoot, Data[1][1], hashedPass)
        else
            LogAlert("EmptyRectangle")
        end
    end

    passwordHash generates a new salt, a salt that does not match with the salt saved on the database, meaning the results will never match. Indeed, you can try this yourself:

    local inputPassword = "somesecretpassword123"
    local hash1 = passwordHash(inputPassword, "bcrypt", {}) -- pretend this one is saved on the database some time ago
    local hash2 = passwordHash(inputPassword, "bcrypt", {}) -- pretend this one was just hashed now
    
    -- Note the following will never match:
    print(hash1)
    print(hash2)
    
    -- What you're doing:
    passwordVerify(hash2, hash1)) -- note, hash2 is not a password but the result of passwordHash given the input password
    
    -- What you should be doing:
    passwordVerify(inputPassword, hash1) -- this is how passwordVerify is supposed to be called

    Thus, you should have the following in the clientside:

    function Login()
        if Data[1][1] ~= "" and Data[1][2] ~= "" then
            triggerServerEvent("attemptLogin", resourceRoot, Data[1][1], Data[1][2])
        else
            LogAlert("EmptyRectangle")
        end
    end

    And keep the current serverside the same.

  7. You can set up a reverse proxy on your server machine if you have access to one. The idea is that you want to add a new HTTPS-enabled port that connects to nginx/Apache web server ("web server"), which in turn connects to the MTA server's HTTP server ("MTA server web portal") using non-secure HTTP (this is mostly fine if both programs run on the same machine, no external networks are used in between and the firewall is properly configured). Once that is set up, you can outright block the MTA server web portal access from all external IP addresses as well for added security. Now, you can point your iframes to this web server and it will display what the MTA server web portal displays.

    There are plenty of good reverse-proxy guides you can find for nginx/Apache. And while you're at it, you can also use this new nginx/Apache server as an external download server (see httpdownloadurl in mtaserver.conf) and enable gzip or zopfli (this one is slower but provides much more significant reduction in file size) compression (MTA clients support receiving compressed resources but the built-in MTA server does not compress resources before sending) to make the download size smaller for the players on your server.

    • Like 1
  8. On 06/05/2023 at 17:16, Shady1 said:
    function onClientPlayerJoin()
      -- Get the player element that just joined
      local joinedPlayer = source
      
      -- Mute the new player's voice for the local player
      setPlayerVoiceIgnore(joinedPlayer, true)
    end
    addEventHandler("onClientPlayerJoin", getRootElement(), onClientPlayerJoin)

     

    The only way this would work is if download priority was set higher for the resource with this code, otherwise this code won't execute until everything downloads and thus voice will be heard until download completes.

    • Like 1
  9. 
    addEventHandler("onClientMarkerHit", getJobMarker, hitMarkerWindow)
    
    -- should be changed to
    
    
    addEventHandler("onClientMarkerHit", getJobMarker, function(hitPlayer, matchingDimension)
        if (hitPlayer == localPlayer and matchingDimension) then
          hitMarkerWindow()
        end
      end
    )

    This protects the window from being created when any element hits the marker, instead only triggering if the element that hits the marker is the client that is running this client-side script.

  10. You should carefully rethink your code design -- this may work for 1 or 2 car options, but as you add the 3rd, 4th, 5th etc, it gets really complicated unnecessarily. Instead, consider reusing existing code with iterations through a table and extracting parts of the code into their own functions.

    Here's a much more flexible version of your code, which does not rely on adding numbers to variable names, writing a cascade of if blocks and bind handlers within bind handlers, etc.

    local models = {
      411, 478, 240 -- add as many models here as you want, and the code will work without any other changes
    }
    
    local displayVehicle, index -- declaring variables as local ahead of time allows to get the speed benefits of `local` while having variables accessible thoughout the current script file (not accessible in other scripts of the same resource though)
    
    local function updateDisplayVehicle()
      if (isElement(displayVehicle)) then -- to prevent generating errors when the vehicle doesn't exist
        destroyElement(displayVehicle) -- delete the existing vehicle if it exists
      end
      local vehModel = models[index] -- get the vehicle model corresponding to the current index
      displayVehicle = createVehicle(vehModel, 2154.45996, -1153.21362, 23.87550) -- spawns a vehicle with the correct vehicle model
    end
    
    local function tryChangeIndex(newIndex)
      if (models[newIndex]) then -- if the given index is valid (i.e. table `models` contains a value for that index)
        index = newIndex -- set current index to the given index
        updateDisplayVehicle() -- and update the currently spawned vehicle
      end
    end
    
    local function nextVehicle()
      tryChangeIndex(index + 1)
    end
    bindKey("arrow_r", "down", nextVehicle)
    
    local function previousVehicle()
      tryChangeIndex(index - 1)
    end
    bindKey("arrow_l", "down", previousVehicle)

    I've tried to provide comments that should help you understand what I'm doing in the code. I hope this helps.

  11. 20 hours ago, FLUSHBICEPS said:

    "Root" is the main script file of a resource that can access everything in MTA it has access to all functions and resources it can create and manipulate elements such as vehicles players and objects

    What nonsense is that? Root is an element sui generis that represents the top of the element tree, that is, all elements descend from it. Most, if not all, functions are recursive down the element tree (except where propagation is disabled), so setVehicleColor(root, ...) would, while returning false (because the function makes no sense on its principal element, root), set the color of every vehicle that descends from it, or in other words, every vehicle in the world. Then there's resource root, from which all elements spawned by one resource descend. These elements can be recalled with getRootElement and getResourceRootElement (and the pre-defined variables root and resourceRoot). You can read more about this on the MTA wiki page for the Element tree.

    Source is the element on which an event is triggered, an element that is the source of that event. Typically this will be the subject of the event name, for example, in the event onPlayerEnterVehicle, the player is the grammatical subject, and is therefore the source of the event. In an event handler -- that is, the function that is called when the event triggers (registered with addEventHandler) -- MTA defines a local variable called source that references said element. For example, in onPlayerEnterVehicle, the source variable references the player element of the player that entered a vehicle. The vehicle's element will be in the event handler's parameters. To look up what the source of an event is, and the parameters of each event, look up the wiki page for that event.

    • Like 1
  12. These errors mean you're attempting to compare incomparable values, for example, a bool with a number. Is true > 1, < 1? It doesn't make sense.

    getElementData returns a 'false' value when there is no data stored under the given key -- all you have to do is prevent the comparison from happening if the returned value is not a number:

    local returnedValue = getElementData(someElement, "someKey")
    
    -- options:
    if (returnedValue) then
      if (returnedValue > 5) then
        -- valid
      end
    end
    
    if (returnedValue and returnedValue > 5) then
      -- valid
    end
    
    -- the following two will also work if the returned value is a string that contains arabic numbers in base-10, e.g. ("1000" (string) => 1000 (number))
    if (tonumber(returnedValue) and tonumber(returnedValue) > 5) then
      -- valid
    end
    
    local returnedValue = tonumber(getElementData(someElement, "someKey"))
    if (returnedValue and returnedValue > 5) then
      -- valid
    end

     

  13. Please use the Portuguese section if you need help in Portuguese.

    Por favor utilize a secção portuguesa se precisar de ajuda em português.


    This post, translated with DeepL:

    sqlite.db "error loading data I think".

    <code>

    theoretically when entering the marker it should display the amount of fuel available in the station but it is displaying ( Could not get the amount of gasoline for the set ) any idea how to make it work correctly

  14. First issue: Lua is parsed and executed from top to bottom; by the time of the setTimer call, timerJail has not been declared or defined. You need to move the setTimer call after the timerJail function definition.

    Second issue: addEventHandler requires an element to bind onto (2nd argument) -- this means which element (and its children if propagation is enabled, which it is by default) should the event fire for -- and in your code it is an nil-value variable source, hence the error. source is defined within an event handler function, so getElementData(source, "jailLoc") is fine, source is the player that spawned. source outside that function, such as in the arguments passed to addEventHandler, it is undefined/nil. Change this to something like root (this is a pre-defined synonym for the value returned by getRootElement()).

    • Thanks 1
  15. You can use Telegram's Bot API. First, if you haven't already, create a bot (by messaging BotFather on Telegram) and use callRemote or fetchRemote (only do this on the server side, for security reasons -- never send the bot's token to a client) to utilize the API.

    Bots cannot initiate conversations, but you can prompt users to start a conversation by displaying a URL (or preferably a QR code) that a user can scan and open the conversations. You can also use a parameter in the QR code that would allow the bot to correlate the Telegram user with the MTA player if you need that.

    You can also create a group chat or a channel and invite the bot into it, and use it to let users read and write to the ingame chat out-of-game.

  16. The commands you listed are incredibly easy to implement yourself (beginner level stuff, may be difficult if you have absolutely not idea what you're doing but once you learn the basics it should be easy), have you even tried? If so, show us your progress/attempts and what your specific issue is. We're not here to script for you.

  17. The callback function for bindKey callback function does not receive a player element. The client-side bindKey's player can only ever be the localPlayer, so you need to change this function signature:

    function burnoutTheTires(player,key)
    
    -- into this
    function burnoutTheTires(key)

    and likewise for unpressedTheKeys,

    function unpressedTheKeys(player)
    
    -- into this
    function unpressedTheKeys()

    You could have discovered this yourself with a very simply debug methodology: debug outputs. See outputDebugString and iprint, (and less frequently used for this purpose but still usable: outputChatBox, outputConsole).

    And get rid of the burnoutTimer table, it is unnecessary, since it only ever stores one value, always under the key equivalent to the localPlayer element. Simply declare a local value (tip: local values, aka upvalues are faster in write and read speed over globals which are actually entries in the _G table and table lookups aren't as fast as upvalues)

    local burnoutTimer
    
    --- ... elsewhere in the code
    burnoutTimer = setTimer(...)
    
    -- ... in another place
    if isTimer(burnoutTimer) then
    	killTimer(burnoutTimer)
    end
    
    -- etc.

    You may choose to search-and-replace all occurrences of "player" with "localPlayer" since that's pre-defined, but it's fine to leave as since it's just a local value pointing to the localPlayer element.

    The following part seems to be remnant of a time when this script was supposed to be serverside?

    for i,v in ipairs(getElementsByType("player")) do
    --bindKey("accelerate","down",burnoutTheTires)
    --bindKey("brake_reverse","down",burnoutTheTires)
     
    bindKey("accelerate","up",unpressedTheKeys)
    bindKey("brake_reverse","up",unpressedTheKeys)
    end

    Anyway, completely unnecessary -- onClientResourceStart is triggered for a running client (at the time of resource start on the server) as well as a joining client (aka resource start on the client at time of join), so this results in duplicated bind calls.

  18. I'd point out @Shady1's solution does not scale well as the whitelist gets bigger.

    It would be more ideal to, on start-up, and then periodically (or on command like refreshwhitelist) download the list, store it in a Lua table in a [serial] = bool structure and look-up that table onPlayerConnect, and cancelEvent(true) if serial not present (which also benefits the server process as a good part of player join sequence is aborted earlier, including the construction of a player element). This way you don't download the whole list every time a player joins, parse it every time a player joins, and iterate thought it every time a player joins. Lua table look-ups benefit from hashing, leading to much faster lookup speeds than iterating thought the list, provided the list is large enough (could be as little as 5 entries for benefits to show up but I haven't tested it) (otherwise hashing could be more expensive).

    • Like 1
  19. if capacete1 then

    This checks whether a value is assigned to capacete1 (this value could be anything -- a string, number, userdata, coroutine, element, etc.), not whether it is a valid value for the purposes of your script.

    If the warning message you get says "expected element", it means you've passed it something that isn't an element (for instance, it could be a userdata reference to an element that's been destroyed by another script). A solution that gets rid of the warning is

    if isElement(capacete1) then
  20. 1 hour ago, Murilo_apa said:

    How? it work... I gonna study this

    Very thanks man ❤️

    Pretty sure the most important part is

    slots[i] = {screenW * x, screenH * (y -  scroll), screenW * 0.0483, screenH * 0.0872}

    replacing

    table.insert(slots, {screenW * x, screenH * (y -  scroll), screenW * 0.0483, screenH * 0.0872})

    The table.insert version was continuously adding new entries into the table rather than updating existing values as you scroll.

  21. First of all, admin/staff_manager is not part of the default set of resources for MTA, so next time, tell us exactly what sort of resource you're using, where you got it from, a link to the documentation/user guide if you read it yourself beforehand)

    Also, please tell us what you've already tried doing to resolve your issue. So far, the error message suggests there's an issue with the resource 'integration' -- is it started? Does it have any errors when it starts? Have you tried restarting it?

  22. Edit: I've misread the question; the following is an answer to how to create an API that lists the players on your server.

    There are a number of ways to do this:

    • Query the ASE port (123 above the server port, e.g. 22126 for a server running on 22003) for the information (I'm not sure how the ASE protocol works, you'd have to research it), or
    • Query the server via its HTTP port (here you would need to create a HTTP web access resource which responds with the list of players in an encoding the API client expects, e.g. JSON) (can trigger anti-flood/anti-spam countermeasures on MTA server side if IP is not whitelisted in mtaserver.conf, as it would try to request a fresh list every time anyone loads a page; ideas for cached storage of fetched data below)
      • this requires allowing guest HTTP access to the server (can expose admin functionality to everyone if not careful, so limit admin/webadmin resource access to authenticated users only), or setting up a dedicated user and logging in with it (password is sent via HTTP authenticate headers in plaintext, unencrypted; can expose admin functionality to eavesdropper on network between API client and MTA server; so limit this users ACL/RPC to read only access)

    or

    • Have the server regularly send updates to a web server and the web server keep a copy of the latest list of players, or
    • Have the server track players on it and update a MySQL database that the webserver connects to.

     

    The answer to your question is generally no, unless you plan to run surveillance on the MTA global server masterlist, query every server's player list and create a comprehensive database of who plays on what servers, and then query such a database.

  23. It's a syntax issue -- you're not supposed to use a colon to separate the key from the value in a table. The correct character is an equals sign:

     local messages = {
        message1 = " Some message",
        message2 = " Some message2",
    } 

    I believe "<name> expected" is referencing OOP style calls like SomeObject:someMethod(), (the only use of the colon in Lua that I can recall of the top of my head), as if you wrote :someMethod() without any object name in front, but Lua expects a object name there.

    • Like 1
×
×
  • Create New...