Jump to content

IIYAMA

Moderators
  • Posts

    6,058
  • Joined

  • Last visited

  • Days Won

    208

Everything posted by IIYAMA

  1. The first version of the MTA definition files for Lua Language Server are now available. 🥳

     

     

  2. I believe you should first understand the underlying problem. They are not stealing your files. You are sending those files to them. When they host your files, this is where they are using your files without permission. This is where I recommend to solve your problem. They are using your files because they are appealing to be used. If you make them less appealing, for example by adding branding to them, this will become advertisement for your server (in their server).
  3. You could try to use the dxDrawPrimitive3D function to mimic the marker and set postGUI to true, just keep in mind the downsides of postGUI. © MTA wiki
  4. You probably already did the following, but just checking. What happens if you visit those URL's manually? (maybe it is asking for a login authorisation)
  5. You have to index the variable veh one time less. Since the pairs loop did the first index already for you. dxDrawText(veh[1].." | "..veh[2], sx/2-370,sy/2-297,sx/2,sy/2, tocolor(255, 255, 255, 220), 3, "default-bold", "left", "top", false, false, false, true )
  6. Sometimes it really helps when you use the iprint function to print the current table. It shows you the current depth and helps you to decide how to index the next table. When I for example I am working with a secret table. iprint(result) > { ["test"] = { { { [133]="found me!" } } } } The first step is to peel it down. Layer 1 iprint(result["test"]) > { { { [133]="found me!" } } } Layer 2 iprint(result["test"][1]) > { { [133]="found me!" } } Layer 3 iprint(result["test"][1][1]) > { [133]="found me!" } Layer 4 iprint(result["test"][1][1][133]) > "found me!"
  7. It helps if you write every table modification inside of a new function. This way you keep your code more readable. The following examples are re-written code. You could use this as a start, but make sure to test it, because I didn't test it for you. local farm = createFarm(123, 1, 1, 1) local horse = createHorse(player, horseId) local farm = getFarmFromID (123) -- If the farm does exist if farm then addHorseToFarm(horse, farm) end local horseCollection = {} -- all horses local farmCollection = {} -- all farms ---@param player userdata ---@param horseId integer ---@return table function createHorse(player, horseId) local horse = { id = horseId } horseCollection[horseId] = horse return horse end ---@param farmId integer ---@param x number ---@param y number ---@param z number ---@return table function createFarm (farmId, x, y, z) local marker = createMarker(x, y, z) local farm = { marker = marker, id = farmId, location = { x, y, z }, owner = nil, creator = nil, creationTime = getTickCount(), price = 0, horses = {} } farmCollection[farmId] = farm return farm end ---@param farmId integer ---@return table|false function getFarmFromID (farmId) return farmCollection[farmId] or false end ---@param farm table ---@param player userdata ---@return table function setFarmCreator (farm, player) farm.creator = player return farm end ---@param horse table ---@param farm table function addHorseToFarm (horse, farm) table.insert(farm, horse) end ---@param horse table ---@param farm table ---@return boolean function isHorseInFarm(horse, farm) for i=1, #farm do if farm[i] == horse then return true end end return false end ---@param farmId integer ---@return boolean function doesFarmExist(farmId) return farmCollection[farmId] and true or false end
  8. @Wasilij0814 You might want to leave a reply, even if you were not able to solve your issue with my response.
  9. https://dev.mysql.com/doc/mysqld-version-reference/en/keywords-8-0.html groups is a reserved keyword in MySQL: 8.0.2 Change: SELECT * FROM groups to SELECT * FROM `groups` You probably will have to fix the other queries as well.
  10. Lua Language Server - Definition files The Lua language server is a powerful tool that enhances the development experience for Lua programming. It provides a comprehensive set of code editing features, including suggestions, auto-completion, and error checking. With the Lua language server, developers can effortlessly navigate through their resource files, access documentation easily, and ensure code correctness by giving warnings. Why should you care? The language server will inform you about all sorts of problems: type mismatches, missing function arguments, missing variables, etc. You have access to a lot of MTA syntax/autocomplete out of the box. The syntax information will remain while writing. You do not have to restart your resource so often in order to validate if everything is working. Type validation Having value type validation in your code editor is one of the main key features of the Lua Language Server. When working with variables, parameters, and arguments in Lua, you are not restricted to specific value types. This flexibility can make mistakes more likely to happen. However, being able to validate those mistakes instantly saves you a lot of time and frustration. Type annotations for your own functions Adding type annotations to your own functions can help improve validation and catch logic mistakes. It is particularly useful when calling functions from different parts of your code, as the annotations provide clarity on the expected input (arguments) and output (return values). Additionally, comments that are placed above or adjacent to a variable or function are visible when hovering over them in another file or line. This can provide helpful information and context when working with the code. How that looks like: How can I quickly add annotations in less than a second? Open the spoiler: AddEventHandler auto-complete Most MTA addEventHandler functions have full eventName autocompletion. And the attached anonymous function is fully autocompleted and typed as well. Navigation features of Lua Language Server It can be time consuming to find out where a (global) function or variable is located. Being able to jump right to it, saves you a lot of time. Other information which you can find in the readme Installation for the Lua Language Server How to use the definition files? Known issues Make sure to always have an empty new line at the end of your files, as recommended in this issue. Currently, the Lua server language definition files do not have a clear separation between serverside functions/events and clientside functions/events. However, it is possible to enforce this separation for specific functions if needed. outputChatBox--[[@as outputChatBox_server]]("Serverside", player) In certain cases, certain functions in the Lua server language definition files may return multiple types, even if you have selected a different syntax. To handle this situation, you can use the `cast` or `as` notation to explicitly specify the desired type or adjust the returned type. See `Casting and as` syntax below. Casting and as In certain situations, you may have a strong understanding of the type(s) that a variable or expression will have. This is where the keywords "cast" and "as" come into play. These keywords enable you to explicitly specify the intended type, ensuring proper type handling. local varName = exampleFunc() ---@cast varName string local varName = exampleFunc() ---@cast varName string | number local varName = exampleFunc() --[[@as string]] local varName = exampleFunc() --[[@as string | number]] Download The definition files can be downloaded here.
  11. Yes, as long as you do not break the code. <script> function onRemotePlayerWasted (arg) { /* ... */ } </script> addEventHandler("onClientPlayerWasted", root, function () local arg = 123 -- Or string "\"123\"" | "'123'" | '"123"' | [["123"]] --[[ Or table through JSON: local arg = '`' .. toJSON({}) .. '`' In JS: const result = JSON.parse(arg) ]] executeBrowserJavascript(browser, [[onRemotePlayerWasted(]] .. arg .. [[);]]) end) *adjusted JSON example, Must be tested through trial and error.
  12. For example you have an html file with: <script> function onRemotePlayerWasted () { /* ... */ } </script> And you inject the following JavaScript to call the function. addEventHandler("onClientPlayerWasted", root, function () executeBrowserJavascript(browser, [[onRemotePlayerWasted();]]) end)
  13. You could try something like this. -- See https://wiki.multitheftauto.com/wiki/ExecuteBrowserJavascript for the rest of browser the code function reloadList (browser) local list = { "a", "b" } local htmlItems = {} for index, value in ipairs(list) do table.insert(htmlItems, [[<li>]] .. value .. [[</li>]]) end local js = [[{ const list = document.createElement("ul") list.innerHTML = `]] .. table.concat(htmlItems, "\n") .. [[`; document.body.append(list); }]] executeBrowserJavascript(browser, js) end addEventHandler("onClientBrowserDocumentReady", browser, function () reloadList (source) end) See: https://wiki.multitheftauto.com/wiki/OnClientBrowserNavigate when having different pages
  14. There is also the executeBrowserJavascript function used to inject JavaScript in to the browser (browser with local context only).
  15. Having very complex winning conditions also makes it harder for your players to understand. Maybe it is for the best to keep things simple.
  16. Something I like to do it is logging the current situation. playerCount in the area (of all teams) score winner And then go manually through the system with the current situation until you find the issue.
  17. I took a look at it. But I am not sure if I understand all the rules. One thing that I can suggest to make it more readable: local example = { ["Grove Street"] = function (score, playerCount) -- Aztecas or Vagos if score > 0 and (playerCount["Aztecas"] > 0 or playerCount["Vagos"] > 0) and playerCount["Aztecas"] ~= playerCount["Vagos"] then if playerCount["Aztecas"] > playerCount["Vagos"] then return "Aztecas" end return "Vagos" end -- or Ballas if score > 0 and playerCount["Ballas"] > 0 or playerCount["Grove Street"] == 0 then return "Ballas" end -- or Grove Street return "Grove Street" end } -- local winner = example[areaTeam](score, playerCount)
  18. It does. The new method is a utility function, which does everything for you. But also for some parts it forces you to be more strict. For example the: protectedKeys table part. For resourceRoot you can also block this, instead of reporting. Saves you some lines of code. addEvent("onRaiseTheRoof", true) addEventHandler("onRaiseTheRoof", resourceRoot, function(arg1, arg2) end, false) -- propagate disable, making sure only resourceRoot can be passed as the source
  19. Bullets do not start from the weapon Muzzle position. The start position is: https://wiki.multitheftauto.com/wiki/GetPedTargetStart And the end position: https://wiki.multitheftauto.com/wiki/GetPedTargetEnd Both can be used to create a vector for the line you are referring to. This is related to the reason why people can shoot around corners without showing more than their elbows. Since the bullets start more to the right of the weapon.
  20. As roaddog mentioned, toJSON is the easiest way to dynamic store data. But keep an eye on the limitations of JSON. Only store JavaScript structured default {} objects or [] arrays, nothing in between! (else it will be converted to the default {} object variant) This would be the most space saving solution. Depending on the amount of players you receive every day, it might be a better solution when there are a lot. Just keep in mind that it will be more work.
  21. If you are worrying about performance, in that case you might consider using MySQL triggers. Those are as close as it gets to your db, making sure you do not cause overhead and align the value-checks with the update moments. https://dev.mysql.com/doc/refman/8.0/en/trigger-syntax.html Create a value-check table / rollback table: Stores the value that it is being incremented (within 30 seconds) Stores the future time, when elapse the incrementing value can be ignored and resets to 0 Create a trigger which triggers on update Do behaviour of value-check table. If the value jumps too much UP: Report to a separate table Freeze account Rollback manually / automatic (not recommended)
  22. Preventing is better than checking for unusual values. Especially when you can't don't keep track of how they have been changed. This could have been done by an exploit instead of real cheats. I personally consider exploiting almost cheating, since the developers should be responsible to deliver decent code. Back to the subject: Value changes in databases are often done by user input. This should be your primary objective. For example if you have a marker that opens a GUI to transfer money: addEvent("onPlayerTransferMoney", true) addEventHandler("onPlayerTransferMoney", resourceRoot, function (marker, transferAccountId) -- Possible cheating? = hard returns if not isElement(marker) then return end local playerX, playerY, playerZ = getElementPosition(client) local markerX, markerY, markerZ = getElementPosition(marker) if getDistanceBetweenPoints3D ( playerX, playerY, playerZ, markerX, markerY, markerZ ) > 10 then return end -- More user friendly returns if not checkPassiveTimer("event:onPlayerTransferMoney", client, 1000) then return outputChatBox(client, "You can only transfer money every 1 sec.") end -- Source code: https://wiki.multitheftauto.com/wiki/CheckPassiveTimer if not transferAccountId then return end -- resume operations -- end) Check if marker exist Check if player is close enough to the marker Limit execution speed (else users can create database lag by spamming the UI button) Building an anti cheat system to check for anomalies in your database should be a secondary objective. In this case you could start with defining limits. For example a money limit. A soft limit to detect possible cheating and a hard limit to define the impossible. For a soft limit you only report to yourself, as it might be very active player. And for a hard limit, you could for example freeze the money. In this case you could inform the user that you are investigating the issue. > As you might not know if the user itself is responsible for the anomaly or if another user is responsible. That user could also be helpful in finding/knowing the exploit that is responsible for the anomaly.
  23. Yes, in every resource that is receiving data from the database manager, you should add an export function with the name dbResponse. (it is important that the name 'dbResponse' is used consistent)
  24. A - Resource requesting data B - Database manager A > B The first export function is db_query, located in resource B, which you have already created. -- Resource A makes this call to resource B exports.database_connection:db_query(id, "SELECT * FROM `players`") <!-- Resource B --> <export function="db_query" type="server" /> B > A The second one is dbResponse, located in resource A, which is where the response is returned to, with: -- Resource B makes this call to resource A call( theSourceResource, "dbResponse", id, result, num_affected_rows, last_insert_id ) <!-- Resource A --> <export function="dbResponse" type="server" /> -- Resource A function dbResponse (id, ...) if not callbacks[id] then return end callbacks[id](...) end Not sure if you want to keep your function naming style, else it will be db_response ofcourse.
  25. The following examples are part of a project I have build before. You will have to set-up: 2 export function (one each resource) For sending (as you have now) And for receiving You will have to generate an unique identifier for each query, so that each request can be wired back to where you want the data. -- Resource A Identifier = {} function Identifier:newGenerator() return setmetatable( { index = 0 }, { __index = Identifier } ) end function Identifier:generate() local index = self.index + 1 self.index = index return "id:" .. getTickCount() .. "|" .. index end -- For new ID's local identifierGenerator = Identifier:newGenerator() -- function () local id = identifierGenerator:generate() -- end Before you send a request you make sure that there is a destination: -- Resource A callbacks = {} -- function () local id = identifierGenerator:generate() -- Set-up the callback destination, anonymous func if you want to keep going where you left of. callbacks[id] = function (...) iprint(...) end exports.database_connection:db_query(id, "SELECT * FROM `players`") -- end Add wiring for receiving and calling back your callback function: -- Resource A function dbResponse (id, ...) if not callbacks[id] then return end callbacks[id](...) end Keep the wiring going in the db resource, so that the ID goes ping pong: -- Resource B function db_query(id, ...) -- The source resource that made this call (important to rename the pre-defined variable, else it will be lost) local theSourceResource = sourceResource if (db_settings.db_connection) then dbQuery(function(query_handle) local result, num_affected_rows, last_insert_id = dbPoll(query_handle, 0) call( theSourceResource, "dbResponse", id, result, num_affected_rows, last_insert_id ) end, db_settings.db_connection, ...) end end
×
×
  • Create New...