Jump to content

DiSaMe

Helpers
  • Posts

    1,447
  • Joined

  • Last visited

  • Days Won

    32

Posts posted by DiSaMe

  1. setWeaponProperty can change the damage property, but it will affect all players. To change the amount of damage done by individual players, you can detect the damage using onClientPedDamage and onClientVehicleDamage and calling setElementHealth to reduce the health further, and you may also want to call killPed if the adjusted health gets to 0 or lower, in order to attribute the kill to the attacking player.

  2. This block:

    for theKey,theSound in pairs(spawnedSounds) do
      --
    end

    was meant to be used separately from the code that starts the sounds. By putting it into the same block, you're stopping the sounds for all peds if the current ped has deadzombie set to true, as it loops through spawnedSounds table.

    I'd say your initial code is close enough, you just need to use spawnedSounds[peds] to refer to the current ped's sound.

    But then the warning will still be there, it happens because calling stopSound destroys the sound element, but the (no-longer-valid) handle is still in spawnedSounds table, so when it gets passed to stopSound the next time, you get a warning. You can remove a value from the table by assigning nil to it, so destroying the current ped's sound would look like this:

    stopSound(spawnedSounds[peds]) -- stop the sound, destroy the sound element
    spawnedSounds[peds] = nil -- remove the no-longer-existing element from the table

    But even then, I can think of scenarios where things go wrong. Like for example the loop is executed every 6 seconds, so you call playSound3D and store the result to spawnedSounds[peds] repeatedly, meaning the sound element from the previous execution is still overwritten by new sound element. So the next stopSound call will only stop the ped's most recent sound. I guess the right way to handle this depends on your needs, but in any case, assuming you clean up the sounds from the table after stopping them, you can check if the sound from earlier executions exists by directly checking the value in the table:

    if spawnedSounds[peds] then
      -- called if the sound exists
    else
      -- called if the sound doesn't exist
    end

     

    • Thanks 1
  3. 3 hours ago, uriid1 said:

    Well, I understand where I went wrong and why it's not working as I expected. But now I'd like to learn a bit more about how asynchronous functions are executed in MTA and in which thread.

    Maybe for a more intuitive understanding, you shouldn't think of them as asynchronous code that executes "alongside" the rest of the code, but rather as code that executes "inside" coroutine.resume. Because that's exactly what happens. When execution enters coroutine.resume, it enters the coroutine, then when it reaches coroutine.yield, it leaves the coroutine and returns from coroutine.resume. Consider this concept of a coroutine-like construct:

    function resumeCustomCoroutine(co)
      local nextFunction = co.functions[co.index]
      nextFunction()
      co.index = co.index+1
    end
    
    local testCo = {
      index = 1,
      functions = {
        function() print("first") end,
        function() print("second") end,
        function() print("third") end
      }
    }
    
    resumeCustomCoroutine(testCo) -- prints: first
    resumeCustomCoroutine(testCo) -- prints: second
    resumeCustomCoroutine(testCo) -- prints: third

    You have no problem understanding that all code inside testCo.functions is executed inside resumeCustomCoroutine, do you? And yet, testCo.functions looks like a piece of code that executes "in parallel" to the code that calls resumeCustomCoroutine. You can think of coroutine.resume the same way, only instead of separate functions, you have "segments" between the start of the function, coroutine.yield calls and the end of the function.

    3 hours ago, uriid1 said:

    Is this thread similar to an event loop, like in Node.js, or does it use a different model? And can you influence this thread, for example, by adding your own asynchronous functions?

    Lua itself has no such thing like JavaScript's event loop. And I don't know how MTA does things internally, but that doesn't matter - it just makes calls into Lua through event handlers or various callbacks, including those passed to fetchRemote. And wherever you call coroutine.resume from, it will advance the coroutine's execution up to the next coroutine.yield call.

    3 hours ago, uriid1 said:

    On an interesting note, there's a project called luvit that also uses an asynchronous model, but they've figured out how to wrap asynchronous functions in coroutines. For example, you can see this in action here: https://github.com/luvit/lit/blob/master/deps/coro-net.lua.

    This is something I would also like to achieve in MTA. However, for now, it seems impossible.

    Actually, if you look at makeCallback in that link, you can see it calls coroutine.running to store the current coroutine into variable thread, which is later passed (indirectly through nested function, then assertResume) to coroutine.resume. connect function uses makeCallback to create such coroutine-resuming function and passes it as a callback to socket:connect, then calls coroutine.yield to pause its own execution. So not only it's possible in MTA - it's the very same thing I did with fetchRemote, only structured slightly differently.

  4. Your start angle is -270. The stop angle is:

    When the health is 100: (100 * 6.3 / 2 - 90) = 225

    When the health is 0: (0 * 6.3 / 2 - 90) = -90

    So the stop angle is always larger than the start angle, making the arc always visible. You need to choose such numbers that the stop angle would match the start angle when the health is 0.

    • Like 2
  5. 10 hours ago, uriid1 said:

    Unfortunately, the example you provided will only work synchronously for the testFetchAndOutput function.

    That's the point, you're supposed to do the work from within the coroutine. testFetchAndOutput is an example of such function. It looks like a single function but executes over multiple invocations of the main thread. So you need to transform testFetchAndOutput into something that does all the work you need to do.

    9 hours ago, uriid1 said:
    local testCo = coroutine.create(testFetchAndOutput)
    
    print(coroutine.status(testCo)) -- suspended
    coroutine.resume(testCo)
    print(coroutine.status(testCo)) -- suspended
    coroutine.resume(testCo)
    print(coroutine.status(testCo)) -- dead

    Well, you shouldn't be resuming this coroutine like that. The coroutine should be resumed when the response data arrives, which the callback in fetchRemoteAsync already takes care of. What you need to do from the main thread is what my example already does, which is creating and starting the coroutine using coroutine.create and a single coroutine.resume call (optionally you can pass arguments to coroutine.resume to be used as arguments for coroutine's function). The rest of the coroutine will execute when response arrives. Until then, the coroutine will be in a suspended state and the main thread (the rest of the stuff that happens in the server, including the code following the initial coroutine.resume call) will be executing.

    That basically turns the coroutine into a thread - not in a technical "OS thread" sense, but in a logical sense. fetchRemoteAsync doesn't execute instantaneously - between calling and returning, the time passes and other things update on the server. Coroutines allow you to avoid callbacks, but they can't just make asynchronous code run synchronously with respect to the main thread. Even if they could - unless it's some initialization code that runs once on start, you don't want to freeze your server in case the remote server takes a while to respond, do you?

    In general, coroutines are one of my favorite Lua features. Unfortunately, they're very unpopular, and I'm hardly aware of sources of more info on this. Maybe searching in http://lua-users.org/wiki/ will give you some results.

    It's not just in Lua that they're unpopular. An analogous feature in other languages that I'm aware of is generator functions in JavaScript (using function*, yield and yield* syntax and iterators) and Python (yield and yield from syntax and iterators). In those languages, it's probably overshadowed by async functions, using async/await syntax - in fact, most likely that's what we would be using for this fetchRemote stuff if it was in those languages instead of Lua.

    Maybe I should make a tutorial on coroutines someday. But in the end, it's a simple concept, the coroutine execution just advances between coroutine.yield calls every time you resume it.

  6. I don't know what you're calling "different addressing". The reason it enters an infinite loop is because multiple threads cannot execute code in the same Lua state at the same time. So the callback cannot stop the loop because the loop needs to end to allow the callback to execute in the first place.

    And even if that wasn't a problem, there's another one: Lua executes synchronously with the rest of the stuff in the server, so even if it wasn't an infinite loop, it would still be blocking the server from updating other things until the response from remote server arrives.

    Instead of calling fetchRemote synchronously, you probably want to execute your own Lua code asynchronously, over multiple Lua invocations. You can achieve that using coroutines, which allow the function execution to stop and resume later.

    An example:

    function fetchRemoteAsync(url, opts)
      local co = coroutine.running() -- Store the current coroutine in a variable
    
      local function callback(responseData, errorCode)
        -- When the response arrives, resume the coroutine stored in variable co. Also
        -- pass responseData and errorCode as values to be returned by coroutine.yield.
        coroutine.resume(co, responseData, errorCode)
      end
    
      fetchRemote(url, opts, callback)
    
      -- Pause the execution of the current coroutine. It will be
      -- resumed when coroutine.resume gets called for this coroutine
      -- and any additional arguments will be returned.
      local responseData, errorCode = coroutine.yield()
    
      -- The coroutine has resumed, return the data.
      return responseData, errorCode
    end
    
    function testFetchAndOutput()
      local responseData, errorCode = fetchRemoteAsync('http://site.com/foo/bar', { method = 'POST', headers = ... })
      outputDebugString(responseData)
    end
    
    -- Create a coroutine that will execute testFetchAndOutput
    local testCo = coroutine.create(testFetchAndOutput)
    
    -- Execute the coroutine
    coroutine.resume(testCo)

    As a result, fetchRemoteAsync isn't blocking so you can call it and everything else on the server (including Lua code) can execute before it returns. But it can only be called from a coroutine.

    • Like 1
  7. Not sure what's the right way to detect when the vehicle drowns - I've been thinking about isElementInWater, but I don't know what exactly that function does. Perhaps it returns true when the vehicle merely touches the water, which isn't enough to count as drowning. If that function doesn't work, then testLineAgainstWater should do the thing. You can check a line from vehicle's position to some point above or something like that. Use getVehicleType to check if the vehicle is a boat or getElementModel in case you want to exclude model IDs, because checking the vehicle type may not be accurate enough - for example, as far as I know, the game considers Vortex a plane (which allows you to control it while in the air). After detecting that a vehicle has drowned, you can make a timer to destroy the vehicle using setTimer and destroyElement.

  8. There seems to be confusion between variables due to a poor choice of names. ped is a table of all peds while peds is the current ped in the iteration. Because of that, the sound handle inserted into the table in one iteration still gets overridden in the other - because ped is the same value between iterations (always refers to the table of all peds). So it's better to swap them. But with the current names, you're supposed to use spawnedSounds[peds] to refer to the current ped's sound, and pass peds to getElementData.

  9. You can also implement your own fires using createEffect. This is going to require more work because you'll have to remake the fire mechanics in Lua (spreading, dying out, setting peds/vehicles on fire, extinguishing using fire extinguisher/firetruck) but the upside is that you'll be in full control - such fires will not spread or die out outside control of your script, and you can make them fully synced between all players.

  10. I'm not sure I can think of a scenario where avoiding such problem would be difficult. If you call triggerClientEvent, then there will be a (usually short) window of time when the state of the checkbox has changed on the client side but the signal hasn't arrived at the server yet. But as long as you pass the desired engine state along with the event data, the checkbox state and the actual engine state should be consistent in the end.

  11. The trolley poles look like custom objects attached to the vehicle using attachElements and their rotation is controlled with setElementAttachedOffsets. Alternatively, they can be parts of custom vehicle model, rotated using setVehicleComponentRotation.

    On every frame (in onClientPreRender event), you would find a particular point of the overhead wire and rotate the trolley poles towards that point, so it would look like they are connected all the time.

    Finding the point of the overhead wire depends on the format of the wire data you're using, but generally it involves finding a sphere-line intersection point. That is, if a sphere is positioned where the trolley pole is mounted and its radius equals the length of the trolley pole, and a line matches the overhead wire, then the sphere-line intersection point is the point that the trolley pole should be rotated towards. That requires some math.

    Calculating rotation from position is another part that requires math - if you have a position in world coordinate system, you can use getElementMatrix to get a matrix that you can then use to get the same position with respect to the vehicle. Then you can use math.atan2 to convert position to rotation angles (in radians), math.deg to convert angles from radians to degrees and pass the resulting values to setElementAttachedOffsets.

    And when the power is disconnected, setVehicleEngineState can be used to disable the engine.

  12. But does the player respawn immediately, with your code, or does it not work either? I don't see grove and police variables being defined anywhere, so if this is the full script, it's understandable why it doesn't work. You either need to store the teams in these variables (for example, you can use getTeamFromName) so one of them would compare equal to team variable, or check the team in some other way, like instead of comparing the team element, you can retrieve its name and compare that, like this:

    local team = getPlayerTeam(source)
    local teamName = getTeamName(team)
    if teamName == "Grove" then
    	-- ...
    end
    if teamName == "Police" then
    	-- ...
    end

    But then it depends on what exactly the names of the teams are, because they have to be exactly the same to compare equal.

  13. My suggestion:

    Use getCameraMatrix to get the x, y, z position of camera.

    Use getWorldFromScreenPosition on position of the cursor, with depth being the maximum distance you want to reach, to get the cursor's 3D position at that distance.

    Use processLineOfSight between camera and cursor coordinates to find the 3D position of the vehicle's surface point where the cursor is pointing.

    Transform that surface point from world coordinate space to vehicle's local coordinate space with the help of getElementMatrix. This one is a bit tricky since the element matrix allows transforming from local to world space easily, using matrix x vector multiplication, but doing the opposite (world to local) requires multiplying the inverse of that matrix by vector, and there doesn't seem to be a built-in function for calculating the inverse of a matrix in MTA. I could do that in Lua, since it's not very complicated, but still easy enough to make a mistake.

    Luckily, the vehicles have a scale of 1 and no shearing transformations (unless some glitch with Monster truck wheels occurs which does exactly that kind of crap to other vehicles), which makes the rotation matrix (the 3x3 part of element matrix) orthogonal, which in turn makes its inverse equal to transpose.

    This is my attempt to transform the coordinates:

    -- wx, wy, wz are the coordinates in world space
    
    local m = getElementMatrix(vehicle)
    
    local rx, ry, rz = wx-m[4][1], wy-m[4][2], wz = m[4][3]
    
    local lx = m[1][1]*rx + m[1][2]*ry + m[1][3]*rz
    local ly = m[2][1]*rx + m[2][2]*ry + m[2][3]*rz
    local lz = m[3][1]*rx + m[3][2]*ry + m[3][3]*rz

    Unless I screwed something up, lx, ly, lz should be wx, wy, wz relative to the vehicle. By transforming the cursor's 3D position like that, you can get the cursor's position with respect to the vehicle, which sounds like what you need.

    • Confused 2
  14. If you need to execute the same code you have, but with a delay, then you can wrap it in a function that's inside a setTimer call, like this:

    function respawn()
    	--[[ source variable won't exist after the event handler finishes
    	so it won't be available inside the timer function, therefore we
    	retain its value by creating a local variable "player" and
    	assigning the value to it ]]
    	local player = source
    
    	setTimer(function()
    		-- whatever code you need to execute
    	end, 10000, 1)
    end
    addEventHandler("onPlayerWasted", getRootElement(), respawn)

    And because the source variable won't exist when the timer executes, you need to replace all occurrences with player.

    • Sad 1
  15. My point was that you shouldn't do that much work on the client. The client-side code should only be used for playing the sound. Which in this case is the part with syncedranfominform function and onPlaySyncedRandInform event. Everything else should be done on the server.

    You should call setTimer from onPlayerVehicleEnter event to start the timer and call killTimer from onPlayerVehicleExit event to stop it - so every time you enter a vehicle, a timer will be created to execute every 2 minutes, with first execution occurring 2 minutes after entering. And when you exit the vehicle, that timer will be destroyed so it will no longer execute. An example:

    local timers = {}
    
    function startTimerOnEnter(vehicle, seat, jacked)
    	if seat == 0 then -- only call it if entered the driver seat
    		local timer = setTimer(whateverFunctionYouNeedToCall, 120000, 0) -- create a timer
    		timers[source] = timer -- store the timer in the table under source, which is the player
    	end
    end
    
    function stopTimerOnExit(vehicle, seat, jacker, forcedByScript)
    	local timer = timers[source] -- retrieve the timer from the table
    
    	if timer then -- if, for whatever reason, the timer wasn't previously created, we need to check it
    		killTimer(timer) -- destroy the timer
    		timers[source] = nil -- clear the field from the table since the timer no longer exists
    	end
    end
    
    addEventHandler("onPlayerVehicleEnter", root, startTimerOnEnter)
    addEventHandler("onPlayerVehicleExit", root, stopTimerOnExit)

    There are going to be some additional cases where you need to clean up, like destroying the timer if the player quits while in the vehicle, as onPlayerVehicleExit won't be triggered. This is just the main part to show what it should look like.

  16. MTA allows setting ped control states, camera angle and some other things, and that's all you need most of the time. Determining what control states and camera angle to set is up to you.

    If you want the ped to follow the player, the main part of that is setting the forwards control state and changing the camera angle to control the direction, regardless of how you determine that direction - so yes, you can do it using A* algorithm.

    And if a ped caused MTA to crash, that must have been a bug, because generally a script is not supposed to crash the game. But I don't remember that consistently happening to me, even though I used to spend a lot of time scripting peds.

  17. This may be an out-of-bounds limitation. Some things don't work correctly outside (-3000; 3000) coordinate range. It's also possible that it's due to the number of pickups, although the streamer is supposed to keep the element count within game limits so you would always see the closest ones. Either way, it just seems that the pickups are buggy. When I had problems with them, I had to resort to using rotating objects instead.

    • Thanks 1
  18. There are functions like addVehicleSirens, setVehicleSirens and a few others that you can find by searching for "siren" in the function lists in the wiki. But I'm not familiar with them and I don't know how flexible they are.

    But you can also create your own emergency lights using createMarker and attach them to a vehicle using attachElements, then control it with setMarkerColor and setMarkerSize, using either setTimer or onClientRender/onClientPreRender with getTickCount for timing, which gives you more control than the built-in siren functionality does.

×
×
  • Create New...