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)
 |