386 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			386 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| -- in-memory spawnpoint array for this script execution instance
 | |
| local spawnPoints = {}
 | |
| 
 | |
| -- auto-spawn enabled flag
 | |
| local autoSpawnEnabled = false
 | |
| local autoSpawnCallback
 | |
| 
 | |
| -- support for mapmanager maps
 | |
| AddEventHandler('getMapDirectives', function(add)
 | |
|     -- call the remote callback
 | |
|     add('spawnpoint', function(state, model)
 | |
|         -- return another callback to pass coordinates and so on (as such syntax would be [spawnpoint 'model' { options/coords }])
 | |
|         return function(opts)
 | |
|             local x, y, z, heading
 | |
| 
 | |
|             local s, e = pcall(function()
 | |
|                 -- is this a map or an array?
 | |
|                 if opts.x then
 | |
|                     x = opts.x
 | |
|                     y = opts.y
 | |
|                     z = opts.z
 | |
|                 else
 | |
|                     x = opts[1]
 | |
|                     y = opts[2]
 | |
|                     z = opts[3]
 | |
|                 end
 | |
| 
 | |
|                 x = x + 0.0001
 | |
|                 y = y + 0.0001
 | |
|                 z = z + 0.0001
 | |
| 
 | |
|                 -- get a heading and force it to a float, or just default to null
 | |
|                 heading = opts.heading and (opts.heading + 0.01) or 0
 | |
| 
 | |
|                 -- add the spawnpoint
 | |
|                 addSpawnPoint({
 | |
|                     x = x, y = y, z = z,
 | |
|                     heading = heading,
 | |
|                     model = model
 | |
|                 })
 | |
| 
 | |
|                 -- recalculate the model for storage
 | |
|                 if not tonumber(model) then
 | |
|                     model = GetHashKey(model, _r)
 | |
|                 end
 | |
| 
 | |
|                 -- store the spawn data in the state so we can erase it later on
 | |
|                 state.add('xyz', { x, y, z })
 | |
|                 state.add('model', model)
 | |
|             end)
 | |
| 
 | |
|             if not s then
 | |
|                 Citizen.Trace(e .. "\n")
 | |
|             end
 | |
|         end
 | |
|         -- delete callback follows on the next line
 | |
|     end, function(state, arg)
 | |
|         -- loop through all spawn points to find one with our state
 | |
|         for i, sp in ipairs(spawnPoints) do
 | |
|             -- if it matches...
 | |
|             if sp.x == state.xyz[1] and sp.y == state.xyz[2] and sp.z == state.xyz[3] and sp.model == state.model then
 | |
|                 -- remove it.
 | |
|                 table.remove(spawnPoints, i)
 | |
|                 return
 | |
|             end
 | |
|         end
 | |
|     end)
 | |
| end)
 | |
| 
 | |
| 
 | |
| -- loads a set of spawn points from a JSON string
 | |
| function loadSpawns(spawnString)
 | |
|     -- decode the JSON string
 | |
|     local data = json.decode(spawnString)
 | |
| 
 | |
|     -- do we have a 'spawns' field?
 | |
|     if not data.spawns then
 | |
|         error("no 'spawns' in JSON data")
 | |
|     end
 | |
| 
 | |
|     -- loop through the spawns
 | |
|     for i, spawn in ipairs(data.spawns) do
 | |
|         -- and add it to the list (validating as we go)
 | |
|         addSpawnPoint(spawn)
 | |
|     end
 | |
| end
 | |
| 
 | |
| local spawnNum = 1
 | |
| 
 | |
| function addSpawnPoint(spawn)
 | |
|     -- validate the spawn (position)
 | |
|     if not tonumber(spawn.x) or not tonumber(spawn.y) or not tonumber(spawn.z) then
 | |
|         error("invalid spawn position")
 | |
|     end
 | |
| 
 | |
|     -- heading
 | |
|     if not tonumber(spawn.heading) then
 | |
|         error("invalid spawn heading")
 | |
|     end
 | |
| 
 | |
|     -- model (try integer first, if not, hash it)
 | |
|     local model = spawn.model
 | |
| 
 | |
|     if not tonumber(spawn.model) then
 | |
|         model = GetHashKey(spawn.model)
 | |
|     end
 | |
| 
 | |
|     -- is the model actually a model?
 | |
|     if not IsModelInCdimage(model) then
 | |
|         error("invalid spawn model")
 | |
|     end
 | |
| 
 | |
|     -- is is even a ped?
 | |
|     -- not in V?
 | |
|     --[[if not IsThisModelAPed(model) then
 | |
|         error("this model ain't a ped!")
 | |
|     end]]
 | |
| 
 | |
|     -- overwrite the model in case we hashed it
 | |
|     spawn.model = model
 | |
| 
 | |
|     -- add an index
 | |
|     spawn.idx = spawnNum
 | |
|     spawnNum = spawnNum + 1
 | |
| 
 | |
|     -- all OK, add the spawn entry to the list
 | |
|     table.insert(spawnPoints, spawn)
 | |
| 
 | |
|     return spawn.idx
 | |
| end
 | |
| 
 | |
| -- removes a spawn point
 | |
| function removeSpawnPoint(spawn)
 | |
|     for i = 1, #spawnPoints do
 | |
|         if spawnPoints[i].idx == spawn then
 | |
|             table.remove(spawnPoints, i)
 | |
|             return
 | |
|         end
 | |
|     end
 | |
| end
 | |
| 
 | |
| -- changes the auto-spawn flag
 | |
| function setAutoSpawn(enabled)
 | |
|     autoSpawnEnabled = enabled
 | |
| end
 | |
| 
 | |
| -- sets a callback to execute instead of 'native' spawning when trying to auto-spawn
 | |
| function setAutoSpawnCallback(cb)
 | |
|     autoSpawnCallback = cb
 | |
|     autoSpawnEnabled = true
 | |
| end
 | |
| 
 | |
| -- function as existing in original R* scripts
 | |
| local function freezePlayer(id, freeze)
 | |
|     local player = id
 | |
|     SetPlayerControl(player, not freeze, false)
 | |
| 
 | |
|     local ped = GetPlayerPed(player)
 | |
| 
 | |
|     if not freeze then
 | |
|         if not IsEntityVisible(ped) then
 | |
|             SetEntityVisible(ped, true)
 | |
|         end
 | |
| 
 | |
|         if not IsPedInAnyVehicle(ped) then
 | |
|             SetEntityCollision(ped, true)
 | |
|         end
 | |
| 
 | |
|         FreezeEntityPosition(ped, false)
 | |
|         --SetCharNeverTargetted(ped, false)
 | |
|         SetPlayerInvincible(player, false)
 | |
|     else
 | |
|         if IsEntityVisible(ped) then
 | |
|             SetEntityVisible(ped, false)
 | |
|         end
 | |
| 
 | |
|         SetEntityCollision(ped, false)
 | |
|         FreezeEntityPosition(ped, true)
 | |
|         --SetCharNeverTargetted(ped, true)
 | |
|         SetPlayerInvincible(player, true)
 | |
|         --RemovePtfxFromPed(ped)
 | |
| 
 | |
|         if not IsPedFatallyInjured(ped) then
 | |
|             ClearPedTasksImmediately(ped)
 | |
|         end
 | |
|     end
 | |
| end
 | |
| 
 | |
| function loadScene(x, y, z)
 | |
| 	if not NewLoadSceneStart then
 | |
| 		return
 | |
| 	end
 | |
| 
 | |
|     NewLoadSceneStart(x, y, z, 0.0, 0.0, 0.0, 20.0, 0)
 | |
| 
 | |
|     while IsNewLoadSceneActive() do
 | |
|         networkTimer = GetNetworkTimer()
 | |
| 
 | |
|         NetworkUpdateLoadScene()
 | |
|     end
 | |
| end
 | |
| 
 | |
| -- to prevent trying to spawn multiple times
 | |
| local spawnLock = false
 | |
| 
 | |
| -- spawns the current player at a certain spawn point index (or a random one, for that matter)
 | |
| function spawnPlayer(spawnIdx, cb)
 | |
|     if spawnLock then
 | |
|         return
 | |
|     end
 | |
| 
 | |
|     spawnLock = true
 | |
| 
 | |
|     Citizen.CreateThread(function()
 | |
|         -- if the spawn isn't set, select a random one
 | |
|         if not spawnIdx then
 | |
|             spawnIdx = GetRandomIntInRange(1, #spawnPoints + 1)
 | |
|         end
 | |
| 
 | |
|         -- get the spawn from the array
 | |
|         local spawn
 | |
| 
 | |
|         if type(spawnIdx) == 'table' then
 | |
|             spawn = spawnIdx
 | |
| 
 | |
|             -- prevent errors when passing spawn table
 | |
|             spawn.x = spawn.x + 0.00
 | |
|             spawn.y = spawn.y + 0.00
 | |
|             spawn.z = spawn.z + 0.00
 | |
| 
 | |
|             spawn.heading = spawn.heading and (spawn.heading + 0.00) or 0
 | |
|         else
 | |
|             spawn = spawnPoints[spawnIdx]
 | |
|         end
 | |
| 
 | |
|         if not spawn.skipFade then
 | |
|             DoScreenFadeOut(500)
 | |
| 
 | |
|             while not IsScreenFadedOut() do
 | |
|                 Citizen.Wait(0)
 | |
|             end
 | |
|         end
 | |
| 
 | |
|         -- validate the index
 | |
|         if not spawn then
 | |
|             Citizen.Trace("tried to spawn at an invalid spawn index\n")
 | |
| 
 | |
|             spawnLock = false
 | |
| 
 | |
|             return
 | |
|         end
 | |
| 
 | |
|         -- freeze the local player
 | |
|         freezePlayer(PlayerId(), true)
 | |
| 
 | |
|         -- if the spawn has a model set
 | |
|         if spawn.model then
 | |
|             RequestModel(spawn.model)
 | |
| 
 | |
|             -- load the model for this spawn
 | |
|             while not HasModelLoaded(spawn.model) do
 | |
|                 RequestModel(spawn.model)
 | |
| 
 | |
|                 Wait(0)
 | |
|             end
 | |
| 
 | |
|             -- change the player model
 | |
|             SetPlayerModel(PlayerId(), spawn.model)
 | |
| 
 | |
|             -- release the player model
 | |
|             SetModelAsNoLongerNeeded(spawn.model)
 | |
|             
 | |
|             -- RDR3 player model bits
 | |
|             if N_0x283978a15512b2fe then
 | |
| 				N_0x283978a15512b2fe(PlayerPedId(), true)
 | |
|             end
 | |
|         end
 | |
| 
 | |
|         -- preload collisions for the spawnpoint
 | |
|         RequestCollisionAtCoord(spawn.x, spawn.y, spawn.z)
 | |
| 
 | |
|         -- spawn the player
 | |
|         local ped = PlayerPedId()
 | |
| 
 | |
|         -- V requires setting coords as well
 | |
|         SetEntityCoordsNoOffset(ped, spawn.x, spawn.y, spawn.z, false, false, false, true)
 | |
| 
 | |
|         NetworkResurrectLocalPlayer(spawn.x, spawn.y, spawn.z, spawn.heading, true, true, false)
 | |
| 
 | |
|         -- gamelogic-style cleanup stuff
 | |
|         ClearPedTasksImmediately(ped)
 | |
|         --SetEntityHealth(ped, 300) -- TODO: allow configuration of this?
 | |
|         RemoveAllPedWeapons(ped) -- TODO: make configurable (V behavior?)
 | |
|         ClearPlayerWantedLevel(PlayerId())
 | |
| 
 | |
|         -- why is this even a flag?
 | |
|         --SetCharWillFlyThroughWindscreen(ped, false)
 | |
| 
 | |
|         -- set primary camera heading
 | |
|         --SetGameCamHeading(spawn.heading)
 | |
|         --CamRestoreJumpcut(GetGameCam())
 | |
| 
 | |
|         -- load the scene; streaming expects us to do it
 | |
|         --ForceLoadingScreen(true)
 | |
|         --loadScene(spawn.x, spawn.y, spawn.z)
 | |
|         --ForceLoadingScreen(false)
 | |
| 
 | |
|         local time = GetGameTimer()
 | |
| 
 | |
|         while (not HasCollisionLoadedAroundEntity(ped) and (GetGameTimer() - time) < 5000) do
 | |
|             Citizen.Wait(0)
 | |
|         end
 | |
| 
 | |
|         ShutdownLoadingScreen()
 | |
| 
 | |
|         if IsScreenFadedOut() then
 | |
|             DoScreenFadeIn(500)
 | |
| 
 | |
|             while not IsScreenFadedIn() do
 | |
|                 Citizen.Wait(0)
 | |
|             end
 | |
|         end
 | |
| 
 | |
|         -- and unfreeze the player
 | |
|         freezePlayer(PlayerId(), false)
 | |
| 
 | |
|         TriggerEvent('playerSpawned', spawn)
 | |
| 
 | |
|         if cb then
 | |
|             cb(spawn)
 | |
|         end
 | |
| 
 | |
|         spawnLock = false
 | |
|     end)
 | |
| end
 | |
| 
 | |
| -- automatic spawning monitor thread, too
 | |
| local respawnForced
 | |
| local diedAt
 | |
| 
 | |
| Citizen.CreateThread(function()
 | |
|     -- main loop thing
 | |
|     while true do
 | |
|         Citizen.Wait(50)
 | |
| 
 | |
|         local playerPed = PlayerPedId()
 | |
| 
 | |
|         if playerPed and playerPed ~= -1 then
 | |
|             -- check if we want to autospawn
 | |
|             if autoSpawnEnabled then
 | |
|                 if NetworkIsPlayerActive(PlayerId()) then
 | |
|                     if (diedAt and (math.abs(GetTimeDifference(GetGameTimer(), diedAt)) > 2000)) or respawnForced then
 | |
|                         if autoSpawnCallback then
 | |
|                             autoSpawnCallback()
 | |
|                         else
 | |
|                             spawnPlayer()
 | |
|                         end
 | |
| 
 | |
|                         respawnForced = false
 | |
|                     end
 | |
|                 end
 | |
|             end
 | |
| 
 | |
|             if IsEntityDead(playerPed) then
 | |
|                 if not diedAt then
 | |
|                     diedAt = GetGameTimer()
 | |
|                 end
 | |
|             else
 | |
|                 diedAt = nil
 | |
|             end
 | |
|         end
 | |
|     end
 | |
| end)
 | |
| 
 | |
| function forceRespawn()
 | |
|     spawnLock = false
 | |
|     respawnForced = true
 | |
| end
 | |
| 
 | |
| exports('spawnPlayer', spawnPlayer)
 | |
| exports('addSpawnPoint', addSpawnPoint)
 | |
| exports('removeSpawnPoint', removeSpawnPoint)
 | |
| exports('loadSpawns', loadSpawns)
 | |
| exports('setAutoSpawn', setAutoSpawn)
 | |
| exports('setAutoSpawnCallback', setAutoSpawnCallback)
 | |
| exports('forceRespawn', forceRespawn)
 | 
