1
0
Fork 0
forked from Simnation/Main
Main/resources/[carscripts]/community_bridge/lib/cutscenes/client/cutscene.lua

395 lines
12 KiB
Lua
Raw Normal View History

2025-08-06 16:37:06 +02:00
-- Cutscene Manager for FiveM
-- Handles cutscene loading, playback, and character management
---@class Cutscene
Cutscenes = Cutscenes or {}
Cutscene = Cutscene or {}
Cutscene.done = true
-- Constants
local LOAD_TIMEOUT <const> = 10000
local FADE_DURATION <const> = 1000
local CUTSCENE_WAIT <const> = 1000
-- Character model definitions
local characterTags <const> = {
{ male = 'MP_1' },
{ male = 'MP_2' },
{ male = 'MP_3' },
{ male = 'MP_4' },
{ male = 'MP_Male_Character', female = 'MP_Female_Character' },
{ male = 'MP_Male_Character_1', female = 'MP_Female_Character_1' },
{ male = 'MP_Male_Character_2', female = 'MP_Female_Character_2' },
{ male = 'MP_Male_Character_3', female = 'MP_Female_Character_3' },
{ male = 'MP_Male_Character_4', female = 'MP_Female_Character_4' },
{ male = 'MP_Plane_Passenger_1' },
{ male = 'MP_Plane_Passenger_2' },
{ male = 'MP_Plane_Passenger_3' },
{ male = 'MP_Plane_Passenger_4' },
{ male = 'MP_Plane_Passenger_5' },
{ male = 'MP_Plane_Passenger_6' },
{ male = 'MP_Plane_Passenger_7' },
{ male = 'MP_Plane_Passenger_8' },
{ male = 'MP_Plane_Passenger_9' },
}
-- Component definitions for character customization
local componentsToSave <const> = {
-- Components
{ name = "head", id = 0, type = "drawable" },
{ name = "beard", id = 1, type = "drawable" },
{ name = "hair", id = 2, type = "drawable" },
{ name = "arms", id = 3, type = "drawable" },
{ name = "pants", id = 4, type = "drawable" },
{ name = "parachute", id = 5, type = "drawable" },
{ name = "feet", id = 6, type = "drawable" },
{ name = "accessories", id = 7, type = "drawable" },
{ name = "undershirt", id = 8, type = "drawable" },
{ name = "vest", id = 9, type = "drawable" },
{ name = "decals", id = 10, type = "drawable" },
{ name = "jacket", id = 11, type = "drawable" },
-- Props
{ name = "hat", id = 0, type = "prop" },
{ name = "glasses", id = 1, type = "prop" },
{ name = "ears", id = 2, type = "prop" },
{ name = "watch", id = 3, type = "prop" },
{ name = "bracelet", id = 4, type = "prop" },
{ name = "misc", id = 5, type = "prop" },
{ name = "left_wrist", id = 6, type = "prop" },
{ name = "right_wrist", id = 7, type = "prop" },
{ name = "prop8", id = 8, type = "prop" },
{ name = "prop9", id = 9, type = "prop" },
}
-- Utility Functions
function table.shallow_copy(t)
local t2 = {}
for k, v in pairs(t) do
t2[k] = v
end
return t2
end
local function WaitForModelLoad(modelHash)
local timeout = GetGameTimer() + LOAD_TIMEOUT
while not HasModelLoaded(modelHash) and GetGameTimer() < timeout do
Wait(0)
end
return HasModelLoaded(modelHash)
end
local function CreatePedFromModel(modelName, coords)
local model = GetHashKey(modelName)
RequestModel(model)
if not WaitForModelLoad(model) then
print("Failed to load model: " .. modelName)
return nil
end
local ped = CreatePed(0, model, coords.x, coords.y, coords.z, 0.0, true, false)
if not DoesEntityExist(ped) then
print("Failed to create ped from model: " .. modelName)
return nil
end
return ped
end
-- Cutscene Core Functions
local function LoadCutscene(cutscene)
assert(cutscene, "Cutscene.Load called without a cutscene name.")
local playbackList = IsPedMale(PlayerPedId()) and 31 or 103
RequestCutsceneWithPlaybackList(cutscene, playbackList, 8)
local timeout = GetGameTimer() + LOAD_TIMEOUT
while not HasCutsceneLoaded() and GetGameTimer() < timeout do
Wait(0)
end
if not HasCutsceneLoaded() then
print("Cutscene failed to load: ", cutscene)
return false
end
return true
end
local function GetCutsceneTags(cutscene)
if not LoadCutscene(cutscene) then return end
StartCutscene(0)
Wait(CUTSCENE_WAIT)
local tags = {}
for _, tag in pairs(characterTags) do
if DoesCutsceneEntityExist(tag.male, 0) or DoesCutsceneEntityExist(tag.female, 0) then
table.insert(tags, tag)
end
end
StopCutsceneImmediately()
Wait(CUTSCENE_WAIT * 2)
return tags
end
-- Character Outfit Management
local function SavePedOutfit(ped)
local outfitData = {}
for _, component in ipairs(componentsToSave) do
if component.type == "drawable" then
outfitData[component.name] = {
id = component.id,
type = component.type,
drawable = GetPedDrawableVariation(ped, component.id),
texture = GetPedTextureVariation(ped, component.id),
palette = GetPedPaletteVariation(ped, component.id),
}
elseif component.type == "prop" then
outfitData[component.name] = {
id = component.id,
type = component.type,
propIndex = GetPedPropIndex(ped, component.id),
propTexture = GetPedPropTextureIndex(ped, component.id),
}
end
end
return outfitData
end
local function ApplyPedOutfit(ped, outfitData)
if not outfitData or type(outfitData) ~= "table" then
print("ApplyPedOutfit: Invalid outfitData provided.")
return
end
for componentName, data in pairs(outfitData) do
if data.type == "drawable" then
SetPedComponentVariation(ped, data.id, data.drawable or 0, data.texture or 0, data.palette or 0)
elseif data.type == "prop" then
if data.propIndex == -1 or data.propIndex == nil then
ClearPedProp(ped, data.id)
else
SetPedPropIndex(ped, data.id, data.propIndex, data.propTexture or 0, true)
end
end
end
end
-- Public Interface
function Cutscene.GetTags(cutscene)
return GetCutsceneTags(cutscene)
end
function Cutscene.Load(cutscene)
return LoadCutscene(cutscene)
end
function Cutscene.SavePedOutfit(ped)
return SavePedOutfit(ped)
end
function Cutscene.ApplyPedOutfit(ped, outfitData)
return ApplyPedOutfit(ped, outfitData)
end
function Cutscene.Create(cutscene, coords, srcs)
local lastCoords = coords or GetEntityCoords(PlayerPedId())
DoScreenFadeOut(0)
local tagsFromCutscene = GetCutsceneTags(cutscene)
if not LoadCutscene(cutscene) then
print("Cutscene.Create: Failed to load cutscene", cutscene)
DoScreenFadeIn(0)
return false
end
srcs = srcs or {}
local clothes = {}
local localped = PlayerPedId()
-- Process players and create necessary peds
local playersToProcess = { { ped = localped, identifier = "localplayer", coords = lastCoords } }
for _, src_raw in ipairs(srcs) do
if type(src_raw) == 'number' then
if src_raw and not DoesEntityExist(src_raw) then
local playerPed = GetPlayerPed(GetPlayerFromServerId(src_raw))
if DoesEntityExist(playerPed) then
local ped = ClonePed(playerPed, false, false, true)
table.insert(playersToProcess, { ped = ped, identifier = "player" })
end
else
table.insert(playersToProcess, { ped = src_raw, identifier = "user" })
end
elseif type(src_raw) == 'string' then
local ped = CreatePedFromModel(src_raw, GetEntityCoords(localped))
if ped then
table.insert(playersToProcess, { ped = ped, identifier = 'script' })
end
end
end
-- Process available tags and assign to players
local availableTags = table.shallow_copy(tagsFromCutscene or {})
local usedTags = {}
local cleanupTags = {}
for _, playerData in ipairs(playersToProcess) do
local currentPed = playerData.ped
if not currentPed or not DoesEntityExist(currentPed) then goto continue end
local tagTable = table.remove(availableTags, 1)
if not tagTable then
print("Cutscene.Create: No available tags for player", playerData.identifier)
break
end
local isPedMale = IsPedMale(currentPed)
local tag = isPedMale and tagTable.male or tagTable.female
local unusedTag = not isPedMale and tagTable.male or tagTable.female -- needs to be this way as default has to be null for missing female
SetCutsceneEntityStreamingFlags(tag, 0, 1)
RegisterEntityForCutscene(currentPed, tag, 0, GetEntityModel(currentPed), 64)
SetCutscenePedComponentVariationFromPed(tag, currentPed, 0)
clothes[tag] = {
clothing = SavePedOutfit(currentPed),
ped = currentPed
}
table.insert(usedTags, tag)
if unusedTag then table.insert(cleanupTags, unusedTag) end
::continue::
end
-- Clean up unused tags
for _, tag in ipairs(cleanupTags) do
local ped = RegisterEntityForCutscene(0, tag, 3, 0, 64)
if ped then
SetEntityVisible(ped, false, false)
end
end
-- Handle remaining unused tags
for _, tag in ipairs(availableTags) do
for _, tagType in pairs({ tag.male, tag.female }) do
if tagType then
local ped = RegisterEntityForCutscene(0, tagType, 3, 0, 64)
if ped then
SetEntityVisible(ped, false, false)
end
end
end
end
return {
cutscene = cutscene,
coords = coords,
tags = usedTags,
srcs = srcs,
peds = playersToProcess,
clothes = clothes,
}
end
function Cutscene.Start(cutsceneData)
if not cutsceneData then
print("Cutscene.Start: Cutscene data is nil.")
return
end
DoScreenFadeIn(FADE_DURATION)
Cutscene.done = false
local clothes = cutsceneData.clothes
local coords = cutsceneData.coords
-- Start cutscene
if coords then
if type(coords) == 'boolean' then
coords = GetEntityCoords(PlayerPedId())
end
StartCutsceneAtCoords(coords.x, coords.y, coords.z, 0)
else
StartCutscene(0)
end
Wait(100)
-- Apply clothing to cutscene characters
for k, datam in pairs(clothes) do
local ped = datam.ped
if DoesEntityExist(ped) then
SetCutscenePedComponentVariationFromPed(k, ped, 0)
ApplyPedOutfit(ped, datam.clothing)
end
end
-- Scene loading thread
CreateThread(function()
local lastCoords
while not Cutscene.done do
local coords = GetWorldCoordFromScreenCoord(0.5, 0.5)
if not lastCoords or #(lastCoords - coords) > 100 then
NewLoadSceneStartSphere(coords.x, coords.y, coords.z, 2000, 0)
lastCoords = coords
end
Wait(500)
end
end)
-- Control blocking thread
CreateThread(function()
while not Cutscene.done do
DisableAllControlActions(0)
DisableFrontendThisFrame()
Wait(3)
end
end)
-- Main cutscene loop
while not HasCutsceneFinished() and not Cutscene.done do
Wait(0)
if IsDisabledControlJustPressed(0, 200) then
DoScreenFadeOut(FADE_DURATION)
Wait(FADE_DURATION)
StopCutsceneImmediately()
Wait(500)
Cutscene.done = true
break
end
end
-- Cleanup
DoScreenFadeIn(FADE_DURATION)
for _, playerData in ipairs(cutsceneData.peds) do
local ped = playerData.ped
if not ped or not DoesEntityExist(ped) then goto continue end
if playerData.identifier == 'script' then
DeleteEntity(ped)
elseif playerData.identifier == 'localplayer' then
SetEntityCoords(ped, playerData.coords.x, playerData.coords.y, playerData.coords.z, false, false, false, false)
end
::continue::
end
Cutscene.done = true
end
-- RegisterCommand('cutscene', function(source, args, rawCommand)
-- local cutscene = args[1]
-- if cutscene then
-- local cutsceneData = Cutscene.Create(cutscene, false, { 1,1,1 })
-- Cutscene.Start(cutsceneData)
-- end
-- end, false)
return Cutscene