Jump to content


  • Posts

  • Joined

  • Last visited

  • Days Won


Addlibs last won the day on September 6

Addlibs had the most liked content!


  • Location
    United Kingdom
  • Occupation
    Scripting Guru

Recent Profile Visitors

5,115 profile views

Addlibs's Achievements


Hard-@ss (35/54)



  1. 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
  2. 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.
  3. 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.
  4. 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.
  5. I'm not sure if this would work, but have you tried setPlayerVoiceBroadcastTo(thePlayer, nil) and setPlayerVoiceIgnoreFrom(thePlayer, root) in onPlayerJoin on the server side? This should be able to control the player's voice chat even if clientside scripts haven't downloaded, but I haven't tested this.
  6. 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.
  7. 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.
  8. 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.
  9. Addlibs


    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.
  10. 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.
  11. 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
  12. 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
  13. 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()).
  14. 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.
  15. 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.
  • Create New...