Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation on 28/11/18 in all areas

  1. After a worrying discussion on Discord last night regarding password storage and remember me functionality I've decided to write a tutorial on how it should be done. This guide assumes you are not using MTA's built in account system, but a custom one. Any code shown in this tutorial is purely for illustrative purposes, and is not finished code, you should use it as a guideline, not a solution. The following topics will be discussed in this tutorial: How to hash and salt passwords (register) How to validate a hashed password (login) How to add "remember-me" functionality How to offer password recovery How to migrate from an older hashing algorithm, to a newer one Using a password policy (Extra) How to handle database leaks (Extra) What even is hashing and salting? For the purpose of this tutorial we expect a database structure which is somewhat similar to this: How to hash and salt passwords When you have a user register on your service, that user expects of you to keep their password safe. Whilst it is generally bad practice to use the same password for multiple services there are many users that still do so. Because of this it's crucial that you save the user's passwords in a way that an attacker will be unable to find out the original password the user typed. This includes if they have full access to your database. In order to do this we do what is called "Password hashing" When a user registers, your server receives the user's intended username, (email) and password. Before you save that password to the database you have to hash and salt this, luckily MTA has a function that takes care of this. If you wish to know more about what exactly it does, there's more information at the end of this tutorial. In order to hash this password you use the passwordHash function. This function is relatively slow (by design), so it is highly recommended you pass a callback to this function, so your entire script doesn't wait for it to complete. https://wiki.multitheftauto.com/wiki/PasswordHash local mysqlHandle -- we're assuming this value is set somewhere else in code function register(username, email, password) local player = client passwordHash(password, "bcrypt", {}, function(hashedPassword) -- callback function for hashing the password local handle = dbExec(function(handle) -- callback function for storing the user in the database if (handle) then triggerClientEvent(player, "registrationSuccess") -- inform the user that registration was successful else triggerClientEvent(player, "registrationFailed") end end,mysqlHandle, "INSERT INTO users (email, username, password) VALUES (?, ?, ?)", email, username, hashedPassword) end) end addEvent("passwordTutorial:register", true) addEventHandler("passwordTutorial:register", getRootElement(), register) How to validate a hashed password Once you've saved the hashed pasword to your database you need to do a little bit of additional work when authenticating the user. Luckily MTA offers a passwordVerify() function, which is the counterpart of the previously discussed passwordHash(). What this function does it basically hashes the password in the same way, resulting in the same output hash. https://wiki.multitheftauto.com/wiki/passwordVerify In order to get the account the user is trying to log in to you have to do a query for an account which has the user submitted username, and of which the password matches through passwordVerify. PasswordVerify is also a relatively slow function, thus you should use a callback. function login(username, password) local player = client dbQuery(function (handle) -- callback for the query selecting the user by username local results = dbPoll(handle, -1) if (#results == 0) then triggerClientEvent(player, "loginFailed") return end passwordVerify(password, results[1].password, {}, function(matches) -- callback function for the password verify if (matches) then -- Do anything you wish with the database result to log the player in with the rest of your scripts triggerClientEvent(player, "loginSuccess") else triggerClientEvent(player, "loginFailed") end end) end, mysqlHandle, "SELECT * FROM users WHERE username = ?", username) end addEvent("passwordTutorial:login", true) addEventHandler("passwordTutorial:login", getRootElement(), login) How to add "remember me" functionality When users on your server log in, they often do not want to have to enter their username and password every time they want to log in. In order to satisfy this need you can implement a "remember me" function. What I've seen happen in the past, is people would store the user's password (encrypted) on the client. This is NOT safe, and should never be done! In order to properly use remember me functionality what you would do is upon login in, generate a random string. The longer the better. This random string is what we call an access token. You would then allow the user to log in with such an access token, preferably only once generating a new access token each time one is used. To implement this you would generate that token every time the user logs in, whilst they have "remember me" enabled. You will have to save this token in your database alongside your user. For extra security you could also store the user's serial alongside the access token, you can then validate that the access token is being used from the same device. https://wiki.multitheftauto.com/wiki/Filepath function login(username, password) -- This code should be put in the callback to the dbQuery function, but to keep the example clean that's not shown here if (rememberMe) then local token = generateRandomToken() dbExec(function() triggerClientEvent(player, "loginSuccess", token) end,mysqlHandle, "INSERT INTO access_tokens (user_id, token) VALUES (?, ?)", results[1].id, token) end end function rememberMeLogin(username, accessToken) -- this function handles a user's login attempt dbQuery(function(handle) local result = dbPoll(handle, -1) if (#result == 0) then triggerClientEvent(player, "loginFailed") else -- Do anything you wish with the database result to log the player in with the rest of your scripts triggerClientEvent(player, "loginSuccess") end end,mysqlHandle, "SELECT users.* FROM access_tokens JOIN users ON users.id = access_tokens.user_id WHERE users.username = ?", username) end addEvent("passwordTutorial:loginRememberMe", true) addEventHandler("passwordTutorial:loginRememberMe", getRootElement(), login) How to offer password recovery Offering password recovery requires a little bit more than just your MTA server. Generally password recovery is done with emails. So you would need access to an email server / service which you can use to send an email from an HTTP request. (Like you can do with fetchRemote()). When a user requests a password reset, have them enter the email you registered with. You then fetch a user from the database with this email address. You would then store a password recovery token for this user. This token, just like the remember me token, is a random string. Ideally, you would send the user a link with a password reset form that goes to a webpage where the user can reset their password. You could do this with an external service, like a webserver. Or you could use MTA's Resource web access for it, but if you do make sure you handle permissions properly for anything else that uses this. However another option would be to have the user copy paste the generated token from the email into you server's login window. Which of the two solutions you pick is up to you, my personal preference goes to the one with the link in the email. But in either case the server side logic is the same. When the user attempts to perform password recovery, verify that the token they give you belongs to a user, and then change the password to the newly requested password. Make sure you hash this password the same way you do in your login. function requestPasswordRecovery(email) dbQuery(function (handle)) local result = dbPoll(handle, -1) if (#result == 0) then triggerClientEvent(player, "passwordTutorial:passwordRecoveryRequestFailed") else local token = generateRandomToken() dbExec(mysqlHandle, "UPDATE user_data SET recovery_token = ?", token) -- mail the token to the user, mail implementation depends on the mail server/service you use triggerClientEvent(player, "passwordTutorial:passwordRecoveryRequestSuccess") end end, mysqlHandle, "SELECT * FROM users WHERE email = ?", email) end function recoverPassword(recoveryToken, password) dbQuery(function (handle) local result = dbPoll(handle, -1) if (#result == 0) then -- this is only valid if you have the user request password recovery from ingame triggerClientEvent(player, "passwordTutorial:passwordRecoveryFailed") else passwordHash(password, "bcrypt", {}, function(hashedPassword) -- callback function for hashing the password local handle = dbExec(function(handle) -- callback function for storing the new password in the database if (handle) then -- this is only valid if you have the user request password recovery from ingame triggerClientEvent(player, "passwordTutorial:passwordRecoverySuccess") -- inform the user that registration was successful else -- this is only valid if you have the user request password recovery from ingame triggerClientEvent(player, "passwordTutorial:passwordRecoveryFailed") end end,mysqlHandle, "UPDATE user_data SET password = ? WHERE recovery_token = ?", username, recoveryToken) end) end end, "SELECT * FROM users WHERE recovery_token = ?", recoveryToken) end Besides changing the password, it's important you also delete any access tokens that user might have if you're using remember me functionality. It is also good practice to make recovery tokens expiry after a certain amount of times, and not allow a recovery token to be created whilst one is already in prgoress. This prevents a user from sending a large number of emails from your service. How to migrate from an older hashing algorithm, to a newer one Maybe after reading this topic you realise that your password security is not what it should be. So you want to change your old password hashing / validation logic to the ones explained in this topic. And due to the nature that hashes can not be "unhashed", you can't simply migrate your passwords over. So in order to migrate the passwords what you have to do is when a user logs in, first validate their password with the old hashing algorithm. If this matches, then hash (and salt) it with your new hashing algorithm and save it in your database. Make sure to delete the old password otherwise your password security is not any better than before. Using a password policy Passwords policies are important to prevent your users from picking a password that is too easily cracked / brute forced. Many password policies come in the form of "Must have at least one capital letter, one digit and one number". But that discards that fact that the best way to make your password more difficult to crack, is making your password longer. So in the code snippet below is a function that measures the 'search space' of a password. The search space of a password is the amount of possible passwords there are with a certain combination of characters. In order to use this, you would have to set a minimum password search space when a user registers for an account. This minimum is up for you to set, but be reasonable, you shouldn't expect a user's password to be impossible to remember / create. I recomend playing with the function a bit to see what values you get out of it, and pick something you believe is sensible. function getPasswordSearchSpace(password) local lowerCase = password:find("%l") and 26 or 0 local upperCase = password:find("%u") and 26 or 0 local digits = password:find("%d") and 10 or 0 local symbols = password:find("%W") and 32 or 0 local length = password:len() return (lowerCase + upperCase + digits + symbols) ^ length end -- The below function calls are to indicate the difference in search space for a set of passwords print(getPasswordSearchSpace("a")) print(getPasswordSearchSpace("abc")) print(getPasswordSearchSpace("Abc")) print(getPasswordSearchSpace("Ab!")) print(getPasswordSearchSpace("Ab!0")) print(getPasswordSearchSpace("Mu#9A0h.")) print(getPasswordSearchSpace("This is a demonstration of how easy an incredibly strong password is to remember")) How to handle database leaks If you have reason to believe that your database has been leaked or otherwise compromised, it is important that your first course of action is removing any access tokens stored in your database. Once you have done that you have to inform your users. Whilst when properly hashed and salted it's extremely difficult / time consuming to find out a user's password it is still a possibilty. So you should inform your users of the breach, tell them that their passwords were properly hashed, and they do not need to fear for their passwords immediately. However you should suggest to your users that they change their password either way, just in case. What even is hashing and salting? Hashing has been brought up several times in this tutorial, whilst you do not need to know what it is / does, you might be interested in knowing regardless. I won't be going too far in depth as I simply do not have the knowledge, but the basic idea of hashing is this: When you hash anything, you turn it into a string of characters (or other value) that has no relation to the original input, other than when you hash the original input again, it will always generate the same hash. For example, when you hash the stirng 'banana' using the sha512 hashing algorithm, it will always yield the output: "F8E3183D38E6C51889582CB260AB825252F395B4AC8FB0E6B13E9A71F7C10A80D5301E4A949F2783CB0C20205F1D850F87045F4420AD2271C8FD5F0CD8944BE3" Now hashing can not be reverted, you can not "unhash" a hash, so in order to verify someone's password you hash it again, and see if the two hashes are the exact same. Now this is great, passwords are safely stored. However there is still more to do, salting. Salting is adding some random data to your password prior to hashing it. This prevents when two users (on the same service, or on others) have the same password, that their hashes are also the same. Meaning if one password is compromised, the other password is not. It is important that a salt is random for every user in your application, not one salt for your entire application. Now you might think we didn't do any salting in the code / tutorial above. This is not true, we just didn't do it ourselves. MTA's passwordHash function actually hashes the passwords and salts it, this salt is then stored in the output hash it self, just before the actual password hash. In the case of bcrypt it actually stores a little bit more info in the resulting hash, but you need not worry about that.
    4 points
  2. Regeneration (health) This resource lets you regenerate player and vehicle* health. It is not an unique idea, I know... but there weren't good implementations for it at the community resource list. So that's why I share this with YOU. * Vehicle regeneration for the driver only. Version 1.0.0 Not compiled! Smooth health regeneration No UI, just the manager Settings (Admin panel) Settings Regeneration [on/off] (player/vehicle) Regeneration value (player/vehicle) Regeneration delay (player/vehicle) Regeneration [on/off] while the vehicle is burning Download link: https://community.multitheftauto.com/?p=resources&s=details&id=15757 Take a quick look into the source code (v1.0.0) Client Server Meta
    1 point
  3. Olá talvez aqui voce entenda como brinca um pouco com ele e largar as db do proprio mta Bom primeiro vamos entender o SQL vamos usar aqui SQLITE e uma versao lite do SQL onde e possivel guarda as info em arquivo .db voce pode usar oque vou ensinar aqui em SQL tb primeiro vamos fazer a connecta ao banco db = dbConnect( "sqlite", "db/banco.db" ) não precisa ter o arquivo o, proprio mta cria ele para voce ? Agora vamos criar as TABELAS e suas COLUNAS dbExec(db, "CREATE TABLE IF NOT EXISTS SERVIDOR (CONTAPLAYER TEXT,SERIAL_PLAYER TXT,SENHA_PLAYER TEXT,DINHEIRO_PLAYER INT)") O codigo acima vai criar uma tabela chamda " SERVIDOR " com 4 colunas vamos entender os tipos de coluna que usei TEXT > E uma coluna do tipo TEXTO INT > E uma coluna do tipo NUMERO INTEIROS ex ( 1 | 33333 | 77777) como eu sei que o dinheiro do player e um numero inteiro botei INT voce não vai precisa coloca isso caso não queira. e melhor para o futuro e tals vou mostra aqui para voce algums argumento de consulta,alteração,e deletar dbQuery(db, "SELECT * FROM SERVIDOR") SELECT > SELECIONA * > TUDO FROM > DE SERVIDOR > NOME DA TABELA nessa ai vimos que eu mandei um comando falando para selecionar tudo da tabela SERVIDOR function SQLiteCall_SERVER () local SV = dbQuery(db, "SELECT * FROM SERVER") if SV then return SV end end aqui um ex de função que vai retorna a tabela selecionada Porem tem que ler ela de uma forma que voce pode ver os dados colocados nela function QualMeuDinheiro(source) local Serial = getPlayerSerial( source ) -- PEGANDO O SERIAL DO PLAYER QUE CHAMO A FUNÇÃO local banco = SQLiteCall_Fazendas() -- CHAMANDO A FUNÇÃO QUE SELECIONA O BANCO DE DADOS if banco then -- VERIFICA SE A TABELA QUE ELE PEGO EXISTE local p = dbPoll(banco,-1) -- BASICAMENTE ELA SERVER PARA RECEBER O RESULTADO DA TABELA for index , dados in ipairs(p) do -- AQUI ABRE A TABELA E TORNA O DADOS POSSIVEL DE LER if Serial == dados["SERIAL_PLAYER"] then -- AQUI TO COMPARANDO O SERIAL COM O DADO LA NO BANCO DE DADOS outputChatBox( "SEU DINHERO E :"..dados["DINHEIRO_PLAYER"],source,255,255,255,true) -- E AQUI FOI SELECIONADO O DINHEIRO DO PLAYER DA VERIFICAÇÃO ACIMA end end end end addCommandHandler( "meudinheiro",QualMeuDinheiro ) a função acima informa o dinheiro do player baseado oque tem no banco de dados essa parte dados["DINHEIRO_PLAYER"] "DINHEIRO_PLAYER" ea nome da coluna la na tabela Ok aqui voce aprendeu a ler os dados e exibir. Agora voce me pergunta e fazer update? dbExec(db, "UPDATE SERVIDOR SET DINHEIRO_PLAYER=? WHERE SERIAL_PLAYER=?",dinheiro,serial) UPDATE > FAZER ALTERAÇÃO SET > QUAL COLUNA DINHEIRO_PLAYER > O NOME DA COLUNA WHERE > E QUAL PARTE | isso que dizer que ele vai pesguisa no banco SERIAL_PLAYER > o nome da tabela que ele vai procura function MoneyMoney(source) local DINHEIRO_NOVO = 10000 local serial = getPlayerSerial( source ) dbExec(db, "UPDATE SERVIDOR SET DINHEIRO_PLAYER=? WHERE SERIAL_PLAYER=?",DINHEIRO_NOVO,serial) -- AQUI FOI FEITO ALTERAÇAO end addCommandHandler( "querodinheiro",MoneyMoney ) essa função faiz o update no banco de dados mais lembre-se se o player tem 50000 la no banco de dados e voce fizer update para 10000 o valor vai ser 10000 então para almenta ou diminuir faça a consulta no banco de dados dps some com o valor novo e assim faiz o update dbExec(db, "INSERT INTO SERVIDOR (CONTAPLAYER,SERIAL_PLAYER,SENHA_PLAYER,DINHEIRO_PLAYER) VALUES(?,?,?,?)",Conta,Serial,Senha,Dinheiro) INSERT > Inserir Into > Em Servidor > Nome da tabela function Megrava () local Conta = "OlaLogin" local Serial = "65D56AS45D5A45D56A4DADAD5A4D" local Senha = "1234b" local Dinheiro = 50000 dbExec(db, "INSERT INTO SERVIDOR (CONTAPLAYER,SERIAL_PLAYER,SENHA_PLAYER,DINHEIRO_PLAYER) VALUES(?,?,?,?)",Conta,Serial,Senha,Dinheiro) --/\ ? são o tanto de argumento que vai por -- end addCommandHandler( "gravar",Megrava ) Nessa parte eu to gravando os dados do player no banco de dados dbExec(db, "DELETE FROM SERVIDOR WHERE SERIAL_PLAYER=?", Serial) -- MESMA IDEIA DO UPDATE MAIS AQUI VOCE DELETA OS DADOS DE UM SERIAL X dbExec(db, "DELETE FROM SERVIDOR") -- AQUI VOCE DELETA TUDO QUE TEM NA TABELA SERVIDOR esse ai e para DELETAR elemento na tabela do banco de dados Eu acho que isso eu basico e todo que voce precisa saber sobre MYSQL qualquer outra duvida pode pergunta @DNL291 DNL se tiver erros de portugues que eu sei que tem kkkk pode corrigir e se tiver confuso pode da uma melhorada se possivel? acho que esse e um tutorial importante ^^
    1 point
  4. Does it matter to be unique? Can two different vehicles have the same license plate? If don't matter: local characters = {"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"} function createRandomPlateText() local plate = "" for i = 1, 3 do plate = plate..characters[math.random(1, #characters)] end plate = plate.."-" for i = 1, 3 do plate = plate..math.random(1, 9) end return plate end
    1 point
  5. تحديث بسيط لطيف خفيف في لوحة التحكم ولدى الزائر الادمن / التحكم في رمز التذكرة كيف يمكن كتابتة وطريقته وعدد الحروف والخ الزائر / البحث عن طريق رمز التذكرة - تفاصيل التذكرة عن طريق رمز التذكرة وبس
    1 point
  6. Eu sinceramente faria automaticamente em vez de ter que digitar o comando, pois acho um saco ter que usar comando pra tudo. Mas enfim, vamos lá: Quando o player encostar no marker, você terá a função com o evento onMarkerHit, que é ativada ao colidir nesse marker, certo? Da mesma forma que teria se fosse iniciar o trabalho automaticamente. Mas em vez de iniciar o trabalho, dentro dessa função você apenas irá colocar o outputChatBox avisando pra ele usar o comando. Na função do addCommandHandler, você deve verificar se o jogador está dentro do marker de início e se ele está a pé, caso contrário ele poderá usar o comando enquanto já estiver trabalhando. Caso ele esteja a pé e esteja dentro do marker então inicia o trabalho, criando o veículo, movendo o player nele e criando o marker de objetivo e setando como data, o próprio jogador como dono dele. Além disso, ainda nesta função, depois de criar o marker de objetivo, você deve adicionar o evento onMarkerHit do marker de objetivo por meio do addEventHandler. Na função do onMarkerHit do marker de objetivo, você deve verificar se o elemento que colidiu nele é o dono do marker, caso contrário qualquer um que colidir nele iria receber a grana do marker. Se colidir no marker e for o dono dele, remove o evento que ativa essa função usando removeEventHandler, destrói o blip, destrói o marker de objetivo e o veículo do trabalho. Então dá o dinheiro pro jogador e pronto. Se o player sair do veículo você deve ter uma função com o evento onVehicleExit, que removerá o evento do marker de objetivo, destruirá o veículo, o blip e o marker de objetivo. local Minicio4 = createMarker (1038, -1338, 12.8, "cylinder", 1, 16, 102, 231, 255) -- Cria o marker onde o player deve usar o comando. local veh = {} -- Cada elemento específico de cada jogador deve estar em uma table para ser criado e acessado corretamente. local Mfim4 = {} -- Marker final específico do player. local Bfim4 = {} -- Blip do marker final específico do player. function inicio4 (hitElement) if hitElement and getElementType (hitElement) == "player" and not getPedOccupiedVehicle (hitElement) then -- Quando o jogador colide no marker e está sem veículo, então: outputChatBox ("Digite /trampo4 para iniciar este emprego.", hitElement) -- Avisa pra ele usar o comando /trampo4 para começar o emprego. end end addEventHandler ("onMarkerHit", Minicio4, inicio4) function startJob4 (thePlayer, cmd) if isElementWithinMarker (thePlayer, Minicio4) and not getPedOccupiedVehicle (thePlayer) then -- Ao usar o comando, só funciona se o jogador estiver no marker e estiver sem veículo. if veh[thePlayer] and isElement (veh[thePlayer]) then -- Se por acaso existir o veículo do trampo do jogador, destroi ele. destroyElement (veh[thePlayer]) veh[thePlayer] = nil end veh[thePlayer] = createVehicle (498, 1009.5, -1355.2, 13.4) -- Cria o veículo do trampo. Mfim4[thePlayer] = createMarker (2801.3, -1088.4, 31 - 1, "cylinder", 2, 0, 255, 0, 255, thePlayer) -- Cria o marker do objetivo do jogador. setElementData (Mfim4[thePlayer], "owner", thePlayer) -- Seta esse jogador como dono do marker, para que só funcione com ele. Bfim4[thePlayer] = createBlipAttachedTo (Mfim4[thePlayer], 19) -- Cria o blip e anexa ao marker. warpPedIntoVehicle (thePlayer, veh[thePlayer]) -- Teleporta o player para o veículo do trampo. outputChatBox ("Leve as rosquinhas ate a bandeira no radar!", thePlayer) -- Avisa o player o que ele tem que fazer agora. addEventHandler ("onMarkerHit", Mfim4[thePlayer], fim4) -- Adiciona o evento que faz funcionar o marker do objetivo. end end addCommandHandler ("trampo4", startJob4) function fim4 (hitElement) if (hitElement == getElementData (source, "owner")) then -- Se o elemento que colidiu for o dono do marker, então: if veh[hitElement] and isElement(veh[hitElement]) then -- Se existe o veículo do trampo do jogador, então: removeEventHandler ("onMarkerHit", Mfim4[hitElement], fim4) -- Remove o evento que ativa este marker, pois ele já funcionou. destroyElement (veh[hitElement]) -- Destroi o veículo do trampo do jogador. givePlayerMoney (hitElement, 2500) -- Dá o dinheiro do trampo. destroyElement (Bfim4[hitElement]) -- Destroi o blip anexado ao marker de objetivo. Bfim4[hitElement] = nil destroyElement (Mfim4[hitElement]) -- Destroi o marker de objetivo. Mfim4[hitElement] = nil outputChatBox ("Você entregou as rosquinhas à fabrica e ganhou R$2500!", hitElement) -- Avisa o jogador que ele completou o trampo. end end end function sair4 (thePlayer) if (veh[thePlayer]) and isElement(veh[thePlayer]) then -- Se o veículo do trampo existe, então: removeEventHandler ("onMarkerHit", Mfim4[thePlayer], fim4) -- Remove o evento que ativa o marker de objetivo. destroyElement (veh[thePlayer]) -- Destroi o veículo do trampo. destroyElement (Bfim4[thePlayer]) -- Destroi o blip de objetivo do trampo. Bfim4[thePlayer] = nil destroyElement (Mfim4[thePlayer]) -- Destroi o marker de objetivo do trampo. Mfim4[thePlayer] = nil outputChatBox ("Você saiu do veiculo e perdeu o trabalho!", thePlayer) -- Avisa o jogador que ele falhou no trampo. end end addEventHandler ("onVehicleExit", getRootElement(), sair4) -- Executa essa função quando o player sair de um veículo qualquer.
    1 point
  7. رح شف خاصك خاصك خاصك خاصك عشان م اهبدك على عينك عينك عينك عينك
    1 point
  8. على كذا ضيف اني اقدر احط صورة بالقريد ليست
    1 point
  9. Na realidade pode, só que você implementou ele de maneira incorreta kkk. Ali não existe necessidade de colocar um loop, um simples if já resolveria: for index, p in ipairs(getElementsByType("player")) do outputChatBox(tostring(getElementData(p, "fome"))) -- debug if getElementData(p, "fome") < 0 then setElementData(p, "fome", 0) end end Obs: Mesmo assim era para o seu código funcionar, provavelmente o elementdata não esteja setado corretamente.
    1 point
  10. Nunca use um while dentro de um loop. Onde vc viu isso?
    1 point
  11. يب انا اصلا معدله بس والله انك بطل بالنسبة لليوزفلات و حرام سيرفرات مثل سيرفراتك تروح و تنتشر
    1 point
  12. Para obter a lista de jogadores online, vc só precisa obter todos os elementos do tipo "player". Para isso você usa getElementsByType ("player") Se você quiser acessar cada jogador obtido por meio de um loop, por exemplo, pra dar um jetpack á cada um deles. Dai você faz assim: function jetPraGeral (thePlayer, cmd) local todoMundo = getElementsByType ("player") -- todoMundo é uma table com todos os jogadores conectados ao server. for i, jogador in ipairs (todoMundo) do -- Para cada elemento dessa table, faça o seguinte: setPedWearingJetpack (jogador, true) -- Dá um jetpack pra esse elemento. end end addCommandHandler ("todosJet", jetPraGeral) A table acima seria tipo assim: local todoMundo = {player1, player2, player3} -- player1 não é o nick do jogador, e sim um elemento do tipo "player". -- Supondo que só tenham 3 jogadores no server. Ali naquele loop do FOR: i se refere ao índice de cada elemento dessa table. No entanto ela não está indexada, então o MTA considera índices inteiros em ordem. Se você quer obter o player2, você pode acessá-lo com todoMundo[2], pois o 2 é o índice que o MTA deu pra ele. jogador se refere a cada elemento em si. ipairs indica que o loop percorre os elementos com índices inteiros numa tabela, em ordem. Obs: Se você quer que o i comece a partir do 0 em vez de 1, use pairs em vez de ipairs. Por exemplo em casos onde você quer contar quantos players estão dentro de um veículo. Porém você deve considerar o assento 0, que é do motorista. function meusOcupantes (thePlayer, cmd) local myVehicle = getPedOccupiedVehicle (thePlayer) -- myVehicle recebe o veículo que o jogador que executou o comando está. if not myVehicle then return end -- Se o jogador que executou o comando não está em um veículo, nada acontece. local counter = 0 -- Contador de ocupantes começa em 0. local ocupantes = getVehicleOccupants(myVehicle) -- ocupantes recebe uma table com todos os jogadores que estão dentro do veículo. Indexada com os assentos ocupados. for seat, player in pairs(ocupantes) do counter = counter + 1 outputChatBox ("Assento "..seat..": "..getPlayerName(player), thePlayer, 255, 255, 255, true) end outputChatBox ("Ocupantes: "..counter, thePlayer) end A table acima é tipo assim: local ocupantes = { [0] = motorista; [1] = player2; [3] = player3 } -- Supondo que não tenha jogador no assento 2. Note que ela está indexada com o assento em que cada jogador está. No entanto se usar ipairs, o motorista seria ignorado, pois o loop começaria a partir do 1, ignorando o índice 0. Então o loop deve ser feito com pairs, que considera todos os elementos independente de seu índice. Porém o loop pairs percorre a table de maneira aleatória, e não em ordem como o ipairs. Outra coisa a ser levada em consideração entre os dois tipos de loop, é que se você estiver usando uma table com índices não-inteiros, o ipairs não pegará nenhum dos elementos, pois ela só funciona com índices inteiros ou com tabelas que não estão indexadas. Faça o seguinte teste entre eles: client-side (recomendável) tabelaNaoIndexada = { -- O próprio MTA irá indexar isso automaticamente em ordem inteira. " ", "Um", 2, "ABC", 0.15 } tabelaIndexadaInt = { -- Funciona com ipairs, mas ignora o 0. [0] = " ", [1] = "Um", [2] = 2, [3] = "ABC", [4] = 0.15 } tabelaIndexadaQualquer = { -- Não funciona com ipairs, pois não é possível determinar a ordem dos índices, pois eles não são todos inteiros. [0] = " ", ["1"] = "Um", ["dois"] = 2, [3.3] = "ABC", [4] = 0.15 } tabelaIndexadaIntQualquer = { -- Só funciona com ipairs até onde existe o índice seguinte. Se não encontrar o índice seguinte, o resto será ignorado. [1] = " ", [2] = "Um", [4] = 2, [5] = "ABC", [6] = 0.15 -- Se substituir o 6 pelo 3 que está faltando, essa table funcionará perfeitamente no ipairs. Pois não terá índice faltando na sequência. } for index, valor in ipairs (tabelaNaoIndexada) do -- Substitua o tabelaNaoIndexada pelas outras tables para testar. Tente também usando o pairs. outputChatBox (index.." = "..valor) end outputChatBox ("Tamanho: "..#tabelaNaoIndexada) -- Tente verificar o tamanho das tables com isso e observe os bugs. A função #table serve para mostrar o tamanho de uma table. No entanto ela não conta os elementos e irá bugar em tables indexadas como os 2 últimos exemplos. Na verdade ela apenas mostra o maior índice inteiro da sequência. No caso do último exemplo, onde o maior índice é 6. Ele vai dizer que o tamanho dela é 6, quando na verdade é 5. Qualquer dúvida que você ainda tiver sobre as tables e os loops, estarei aqui pra ajudar.
    1 point
  13. @VazErn As long as it works, people are always happy. A few tips: A loop for this is not required, unless there are multiple similar database items. for i, id in pairs(queryTable) do setElementData(source, "ID", id["id"]) end setElementData(source, "ID", queryTable[1]["id"]) (untested, but should be working) ------------------------------------------------------------------------------------------------ The sky is the LIMIT If the database has already found what it is looking for, then why not stop looking for more? LIMIT 1 Read more / how to use: https://blog.udemy.com/sql-limit/ ------------------------------------------------------------------------------------------------ Timers are working asynchronous What would happen if a player leaves during those 3 or 14 seconds when the timers are still running? setTimer(setCameraTarget, 3000, 1, source, source) setTimer(triggerClientEvent, 14000, 1, source, "renderRoyalID", source) Just think about it. setTimer(function (element) if isElement(element) then -- do your thing end end, 3000, 1, source)
    1 point
  14. Código devidamente indentado é assim: local Minicio4 = createMarker (1038.02332, -1337.93970, 12.8, "cylinder", 1, 16, 102, 231, 100) local Mfim4 = createMarker ( 2801.29663, -1088.36243, 30.72142 -1, "cylinder", 2, 0 ,255 ,0, 255 ) local Bfim4 = createBlipAttachedTo ( Mfim4, 19 ) setElementVisibleTo ( Bfim4, root, false ) local veh = {} function incio4 (source) if isElementWithinMarker(source, Minicio4) then if veh[source] and isElement( veh[source] ) then destroyElement ( veh[source] ) veh[source] = nil end x,y,z = getElementPosition(source) Trabalho = true veh[source] = createVehicle(498,1009.50598, -1355.16431, 13.34428) setElementVisibleTo ( Bfim4, source, true ) warpPedIntoVehicle ( source, veh[source] ) dxMsg(source, "Leve as rosquinhas ate a bandeira no radar!", "info") end end addEventHandler( "onMarkerHit", Minicio4, incio4 ) function fim4 (source) if veh[source] and isElement(veh[source]) then destroyElement (veh[source]) givePlayerMoney(source,2500) -------------- Caso queira mudar o dinheiro que o player vai ganhar ao finalizar o trabalho setElementVisibleTo ( Bfim4, source, false ) setElementVisibleTo ( entregafinalizar, source, false ) dxMsg(source, "Você entregou as rosquinhas a fabrica e ganhou R$2500!", "sucess") -- else Por que esse else aqui? ... end end addEventHandler("onMarkerHit",Mfim4 ,fim4) function sair4 (source) if (veh[source]) and isElement(veh[source]) then setElementVisibleTo ( Bfim4, source, false ) destroyElement (veh[source]) dxMsg(source, "Você saiu do veiculo e perdeu o trabalho!", "error") -- else Por que esse else aqui de novo? ... end end addEventHandler ( "onVehicleExit", getRootElement(), sair4 ) function dxMsg(source, text, type) exports.dxmessages:outputDx(source, text, type) end  Agora me responda as perguntas que lhe fiz no script, deixei 2 linhas comentadas queria sua explicação. Outra coisa não vi nada declarado relacionado a 'entregafinalizar' e no script você usa setElementVisibleTo ( entregafinalizar, source, false )
    1 point
  15. انها طريقة جديده للنشر أخي في الله بدون روت ?
    1 point
  16. This script has been stolen from a server by taking the uncompiled client.lua from the client resources directory while playing there. As this happens to be my server, I took this opportunity to publish the script on community rather than having someone else show off with work that isn't theirs. Said server has been running this script for a couple of years already. You can download the resource at https://community.multitheftauto.com/index.php?p=resources&amp;s=details&amp;id=15749 now (open source). Topic locked because this practice isn't allowed on the forums Arabic translation (not by me): هذا السكربت تمت سرقته من السيرفر الخاص بي عن طريق سرقة ملف الكلاينت الغير مشفر من مجلد المودات أثناء اللعب هناك, وبالصدفة هذا السكربت خاص بسيرفري لذلك استغليت الفرصة وقمت بنشر السكربت في الكوميونيتي بدلاً من تظاهر شخص بعمل ليس له, هذا السكربت يعمل بالسيرفر لعدة سنين حالياً https://community.multitheftauto.com/index.php?p=resources&amp;s=details&amp;id=15749 : ( تحميل السكربت ( مفتوح المصدر .هذا الموضوع مغلق لأن هذا التصرف ممنوع في المنتدى
    1 point
  17. marker1 = createMarker( 364, 174, 1009, "cylinder", 1.5, 255, 255, 0, 170) setElementInterior( marker1, 3 ) setElementDimension( marker1, 14343 )
    1 point
  18. خلال الأيام القادمة سوف يتم إفتتاح سيرفريً مع حصريات كاملة وجديدة صورة شبيهة بالسيرفر تماماً التحميل راح يكون بين 300 وال 200 ميغا قبل الإفتتاح اي سيرفر يبينا نكون معه الخاص مفتوح
    0 points
×
×
  • Create New...