-- 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) 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 the model a ped? if not IsModelAPed(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, 0) local ped = GetPlayerPed(player) if not freeze then if not IsEntityVisible(ped) then SetEntityVisible(ped, true, false) end if not IsPedInAnyVehicle(ped, false) then SetEntityCollision(ped, true, true) end FreezeEntityPosition(ped, false) --SetCharNeverTargetted(ped, false) SetPlayerInvincible(player, false) else if IsEntityVisible(ped) then SetEntityVisible(ped, false, false) end SetEntityCollision(ped, false, false) FreezeEntityPosition(ped, true) --SetCharNeverTargetted(ped, true) SetPlayerInvincible(player, true) 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 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)