diff --git a/server-data/resources/[esx_addons]/generic_texture_renderer_gfx/fxmanifest.lua b/server-data/resources/[esx_addons]/generic_texture_renderer_gfx/fxmanifest.lua new file mode 100644 index 000000000..242789006 --- /dev/null +++ b/server-data/resources/[esx_addons]/generic_texture_renderer_gfx/fxmanifest.lua @@ -0,0 +1,4 @@ +fx_version "cerulean" +game "gta5" +lua54 'yes' +version "1.0.2" \ No newline at end of file diff --git a/server-data/resources/[esx_addons]/generic_texture_renderer_gfx/stream/generic_texture_renderer.gfx b/server-data/resources/[esx_addons]/generic_texture_renderer_gfx/stream/generic_texture_renderer.gfx new file mode 100644 index 000000000..8151eff54 Binary files /dev/null and b/server-data/resources/[esx_addons]/generic_texture_renderer_gfx/stream/generic_texture_renderer.gfx differ diff --git a/server-data/resources/[esx_addons]/generic_texture_renderer_gfx/stream/generic_texture_renderer_2.gfx b/server-data/resources/[esx_addons]/generic_texture_renderer_gfx/stream/generic_texture_renderer_2.gfx new file mode 100644 index 000000000..8151eff54 Binary files /dev/null and b/server-data/resources/[esx_addons]/generic_texture_renderer_gfx/stream/generic_texture_renderer_2.gfx differ diff --git a/server-data/resources/[esx_addons]/ptelevision/README.md b/server-data/resources/[esx_addons]/ptelevision/README.md new file mode 100644 index 000000000..fd3ded86b --- /dev/null +++ b/server-data/resources/[esx_addons]/ptelevision/README.md @@ -0,0 +1,27 @@ +
+

More Information & Scripts can be found here!

+ +## What is this? + +Basically, this serves as a free resource for servers that need a television script. + +With this resource, you will be able to do the following: + +- Watch Youtube & Twitch Videos / Streams. +- Broadcast your Youtube / Twitch Stream as a server-wide channel. +- Display & Browse the Web. +- Display Images and Videos (via direct link in the browser). + +## What do I need? + +You will need the following for this script to work. + +- [Ox Lib](https://github.com/overextended/ox_lib/releases) (works with any framework) + +- [Renderer](https://forum.cfx.re/t/release-generic-dui-2d-3d-renderer/131208) (works with any framework) + +If you want to make a fork, please obtain permission with a valid reason. + +## Need Support? + +Click here! diff --git a/server-data/resources/[esx_addons]/ptelevision/client/cursor.lua b/server-data/resources/[esx_addons]/ptelevision/client/cursor.lua new file mode 100644 index 000000000..cdf51cf3b --- /dev/null +++ b/server-data/resources/[esx_addons]/ptelevision/client/cursor.lua @@ -0,0 +1,88 @@ +local scale = 1.5 +local screenWidth = math.floor(1920 / scale) +local screenHeight = math.floor(1080 / scale) +shouldDraw = false + +function SetInteractScreen(bool) + if not shouldDraw and bool then + shouldDraw = bool + Citizen.CreateThread(function() + -- Create screen + local nX = 0 + local nY = 0 + + local w, h = screenWidth, screenHeight + + local minX, maxX = ((w - (w / 2)) / 2), (w - (w / 4)) + local totalX = minX - maxX + + local minY, maxY = ((h - (h / 2)) / 2), (h - (h / 4)) + local totalY = minY - maxY + + RequestTextureDictionary("fib_pc") + + -- Update controls while active + while shouldDraw do + nX = GetControlNormal(0, 239) * screenWidth + nY = GetControlNormal(0, 240) * screenHeight + DisableControlAction(0, 1, true) -- Disable looking horizontally + DisableControlAction(0, 2, true) -- Disable looking vertically + DisablePlayerFiring(PlayerPedId(), true) -- Disable weapon firing + DisableControlAction(0, 142, true) -- Disable aiming + DisableControlAction(0, 106, true) -- Disable in-game mouse controls + -- Update mouse position when changed + DrawSprite("ptelevision_b_dict", "ptelevision_b_txd", 0.5, 0.5, 0.5, 0.5, 0.0, 255, 255, 255, 255) + if nX ~= mX or nY ~= mY then + mX = nX + mY = nY + local duiX = -screenWidth * ((mX - minX) / totalX) + local duiY = -screenHeight * ((mY - minY) / totalY) + BlockWeaponWheelThisFrame() + if not (mX > 325) then + mX = 325 + end + if not (mX < 965) then + mX = 965 + end + if not (mY > 185) then + mY = 185 + end + if not (mY < 545) then + mY = 545 + end + SendDuiMouseMove(duiObj, math.floor(duiX), math.floor(duiY)) + end + DrawSprite("fib_pc", "arrow", mX / screenWidth, mY / screenHeight, 0.005, 0.01, 0.0, 255, 255, 255, 255) + + -- Send scroll and click events to dui + + if IsControlPressed(0, 177) then + SetInteractScreen(false) + OpenTVMenu() + end -- scroll up + if IsControlPressed(0, 172) then + SendDuiMouseWheel(duiObj, 10, 0) + end -- scroll up + if IsControlPressed(0, 173) then + SendDuiMouseWheel(duiObj, -10, 0) + end -- scroll down + + if IsDisabledControlJustPressed(0, 24) then + SendDuiMouseDown(duiObj, "left") + elseif IsDisabledControlJustReleased(0, 24) then + SendDuiMouseUp(duiObj, "left") + SendDuiMouseUp(duiObj, "right") + end + if IsDisabledControlJustPressed(0, 25) then + SendDuiMouseDown(duiObj, "right") + elseif IsDisabledControlJustReleased(0, 24) then + SendDuiMouseUp(duiObj, "right") + end + + Wait(0) + end + end) + else + shouldDraw = bool + end +end diff --git a/server-data/resources/[esx_addons]/ptelevision/client/dui.lua b/server-data/resources/[esx_addons]/ptelevision/client/dui.lua new file mode 100644 index 000000000..8a5dab7d7 --- /dev/null +++ b/server-data/resources/[esx_addons]/ptelevision/client/dui.lua @@ -0,0 +1,44 @@ +function CreateNamedRenderTargetForModel(name, model) + local handle = 0 + if not IsNamedRendertargetRegistered(name) then + RegisterNamedRendertarget(name, 0) + end + if not IsNamedRendertargetLinked(model) then + LinkNamedRendertarget(model) + end + if IsNamedRendertargetRegistered(name) then + handle = GetNamedRendertargetRenderId(name) + end + return handle +end + +function RequestTextureDictionary(dict) + RequestStreamedTextureDict(dict) + while not HasStreamedTextureDictLoaded(dict) do + Wait(0) + end + return dict +end + +function LoadModel(model) + if not IsModelInCdimage(model) then + return + end + RequestModel(model) + while not HasModelLoaded(model) do + Wait(0) + end + return model +end + +function RenderScaleformTV(renderTarget, scaleform, entity) + SetTextRenderId(renderTarget) -- set render target + Set_2dLayer(4) + SetScriptGfxDrawBehindPausemenu(1) + --DrawRect(0.5, 0.5, 1.0, 0.5, 255, 0, 0, 255); -- WOAH! + local coords = GetEntityCoords(entity) + local rot = GetEntityRotation(entity) + DrawSprite("ptelevision_b_dict", "ptelevision_b_txd", 0.5, 0.5, 1.0, 1.0, 0.0, 255, 255, 255, 255) + SetTextRenderId(GetDefaultScriptRendertargetRenderId()) -- reset + SetScriptGfxDrawBehindPausemenu(0) +end diff --git a/server-data/resources/[esx_addons]/ptelevision/client/main.lua b/server-data/resources/[esx_addons]/ptelevision/client/main.lua new file mode 100644 index 000000000..75b124de0 --- /dev/null +++ b/server-data/resources/[esx_addons]/ptelevision/client/main.lua @@ -0,0 +1,228 @@ +DEFAULT_URL = "https://cfx-nui-ptelevision/html/index.html" +duiUrl = DEFAULT_URL +duiObj = nil +tvObj = nil +volume = 0.5 +CURRENT_SCREEN = nil + +local Locations = Config.Locations + +function getDuiURL() + return duiUrl +end + +function SetVolume(coords, num) + volume = num + SetTelevisionLocal(coords, "volume", num) +end + +function GetVolume(dist, range) + if not volume then + return 0 + end + local rem = (dist / range) + rem = rem > volume and volume or rem + local _vol = math.floor((volume - rem) * 100) + return _vol +end + +function setDuiURL(url) + duiUrl = url + SetDuiUrl(duiObj, duiUrl) +end + +local sfName = "generic_texture_renderer" + +local width = 1280 +local height = 720 + +local sfHandle = nil +local txdHasBeenSet = false + +function loadScaleform(scaleform) + local scaleformHandle = RequestScaleformMovie(scaleform) + + while not HasScaleformMovieLoaded(scaleformHandle) do + scaleformHandle = RequestScaleformMovie(scaleform) + Citizen.Wait(0) + end + return scaleformHandle +end + +function ShowScreen(data) + CURRENT_SCREEN = data + sfHandle = loadScaleform(sfName) + runtimeTxd = "ptelevision_b_dict" + + local txd = CreateRuntimeTxd("ptelevision_b_dict") + duiObj = CreateDui(duiUrl, width, height) + local dui = GetDuiHandle(duiObj) + local tx = CreateRuntimeTextureFromDuiHandle(txd, "ptelevision_b_txd", dui) + + Citizen.Wait(10) + + PushScaleformMovieFunction(sfHandle, "SET_TEXTURE") + + PushScaleformMovieMethodParameterString("ptelevision_b_dict") + PushScaleformMovieMethodParameterString("ptelevision_b_txd") + + PushScaleformMovieFunctionParameterInt(0) + PushScaleformMovieFunctionParameterInt(0) + PushScaleformMovieFunctionParameterInt(width) + PushScaleformMovieFunctionParameterInt(height) + + PopScaleformMovieFunctionVoid() + Citizen.CreateThread(function() + TriggerServerEvent("ptelevision:requestSync", data.coords) + local tvObj = data.entity + local screenModel = Config.Models[data.model] + while duiObj do + if tvObj and sfHandle ~= nil and HasScaleformMovieLoaded(sfHandle) then + local pos = GetEntityCoords(tvObj) + local scale = screenModel.Scale + local offset = GetOffsetFromEntityInWorldCoords(tvObj, screenModel.Offset.x, screenModel.Offset.y, screenModel.Offset.z) + if screenModel.Target then + local id = CreateNamedRenderTargetForModel(screenModel.Target, data.model) + if id ~= -1 then + RenderScaleformTV(id, sfHandle, tvObj) + end + else + local hz = GetEntityHeading(tvObj) + DrawScaleformMovie_3dSolid(sfHandle, offset, 0.0, 0.0, -hz, 2.0, 2.0, 2.0, scale * 1, scale * (9 / 16), 2) + end + end + Citizen.Wait(0) + end + end) + Citizen.CreateThread(function() + local screen = CURRENT_SCREEN + local modelData = Config.Models[screen.model] + local coords = screen.coords + local range = modelData.Range + local _, lstatus = GetTelevisionLocal(coords) + if lstatus and lstatus.volume then + SetVolume(coords, lstatus.volume) + else + SetVolume(coords, modelData.DefaultVolume) + end + while duiObj do + local pcoords = GetEntityCoords(PlayerPedId()) + local dist = #(coords - pcoords) + SendDuiMessage( + duiObj, + json.encode({ + setVolume = true, + data = GetVolume(dist, range, volume), + }) + ) + Citizen.Wait(100) + end + end) +end + +function HideScreen() + CURRENT_SCREEN = nil + if duiObj then + DestroyDui(duiObj) + SetScaleformMovieAsNoLongerNeeded(sfHandle) + duiObj = nil + sfHandle = nil + end +end + +function GetClosestScreen() + local objPool = GetGamePool("CObject") + local closest = { dist = -1 } + local pcoords = GetEntityCoords(PlayerPedId()) + for i = 1, #objPool do + local entity = objPool[i] + local model = GetEntityModel(entity) + local data = Config.Models[model] + if data then + local coords = GetEntityCoords(entity) + local dist = #(pcoords - coords) + if (dist < closest.dist or closest.dist < 0) and dist < data.Range then + closest = { dist = dist, coords = coords, model = model, entity = entity } + end + end + end + return (closest.entity and closest or nil) +end + +Citizen.CreateThread(function() + Citizen.Wait(2000) + TriggerServerEvent("ptelevision:requestUpdate") + while true do + local wait = 2500 + local data = GetClosestScreen() + if data and not duiObj then + ShowScreen(data) + elseif (not data or #(v3(CURRENT_SCREEN.coords) - v3(data.coords)) > 0.01) and duiObj then + HideScreen() + end + Citizen.Wait(wait) + end +end) + +Citizen.CreateThread(function() + while true do + local wait = 2500 + for i = 1, #Locations do + local data = Locations[i] + local dist = #(GetEntityCoords(PlayerPedId()) - v3(data.Position)) + if not Locations[i].obj and dist < 20.0 then + LoadModel(data.Model) + Locations[i].obj = CreateObject(data.Model, data.Position.x, data.Position.y, data.Position.z) + SetEntityHeading(Locations[i].obj, data.Position.w) + FreezeEntityPosition(Locations[i].obj, true) + elseif Locations[i].obj and dist > 20.0 then + DeleteEntity(Locations[i].obj) + Locations[i].obj = nil + end + end + Citizen.Wait(wait) + end +end) + +RegisterNetEvent("ptelevision:requestUpdate", function(data) + Televisions = data.Televisions + Channels = data.Channels +end) + +RegisterNetEvent("ptelevision:requestSync", function(coords, data) + local tvObj = data.entity + + local _, status = GetTelevision(coords) + local screenModel = Config.Models[data.model] + if status and status["ptv_status"] then + local update_time = status.update_time + local status = status["ptv_status"] + Citizen.Wait(1000) + if status.type == "play" then + if status.channel and Channels[status.channel] then + PlayVideo({ url = Channels[status.channel].url, channel = status.channel }) + elseif status.url then + local time = math.floor(data.current_time - update_time) + PlayVideo({ url = status.url, time = time }) + end + elseif status.type == "browser" then + PlayBrowser({ url = status.url }) + end + end +end) + +RegisterNUICallback("pageLoaded", function(cb) + waitForLoad = false + if cb then + cb() + end +end) + +AddEventHandler("onResourceStop", function(name) + if name == GetCurrentResourceName() then + HideScreen() + for i = 1, #Locations do + DeleteEntity(Locations[i].obj) + end + end +end) diff --git a/server-data/resources/[esx_addons]/ptelevision/client/tv.lua b/server-data/resources/[esx_addons]/ptelevision/client/tv.lua new file mode 100644 index 000000000..0a52c02df --- /dev/null +++ b/server-data/resources/[esx_addons]/ptelevision/client/tv.lua @@ -0,0 +1,240 @@ +TelevisionsLocal = {} + +function SetChannel(index) + TriggerServerEvent("ptelevision:event", CURRENT_SCREEN, "ptv_status", { + type = "play", + channel = index, + }) +end + +function GetChannelList() + if not Channels then + return {} + end + local channel_list = {} + local menu_list = {} + local current = 1 + local screen = CURRENT_SCREEN + local ent = screen.entity + local _, status = GetTelevision(screen.coords) + local channel = nil + if status then + channel = status.channel + end + for index, value in pairs(Channels) do + table.insert(channel_list, { index = index, url = value.url }) + table.insert(menu_list, "Channel #" .. index .. " (" .. value.name .. ")") + if channel ~= nil and channel == index then + current = #channel_list + end + end + return { list = channel_list, display = menu_list, current = current } +end + +function BroadcastMenu() + local _source = GetPlayerServerId(PlayerId()) + for k, v in pairs(Channels) do + if v.source == _source then + TriggerServerEvent("ptelevision:broadcast", nil) + return + end + end + local input = lib.inputDialog("Live Broadcast", { "Channel Name:", "Broadcast URL:" }) + if input[1] and input[2] then + TriggerServerEvent("ptelevision:broadcast", { name = input[1], url = input[2] }) + end +end + +function WebBrowserMenu() + lib.hideMenu() + local input = lib.inputDialog("Web Browser", { "URL:" }) + + if input then + TriggerServerEvent("ptelevision:event", CURRENT_SCREEN, "ptv_status", { + type = "browser", + url = input[1], + }) + end + Citizen.Wait(300) + OpenTVMenu() +end + +function VideoMenu() + lib.hideMenu() + local input = lib.inputDialog("Video Player", { "URL:" }) + if input then + TriggerServerEvent("ptelevision:event", CURRENT_SCREEN, "ptv_status", { + type = "play", + url = input[1], + }) + end + Citizen.Wait(300) + OpenTVMenu() +end + +function VolumeMenu() + lib.hideMenu() + local input = lib.inputDialog("Volume", { "Set Volume (0-100):" }) + if tonumber(input[1]) then + local coords = CURRENT_SCREEN.coords + SetVolume(coords, tonumber(input[1]) / 100) + end + Citizen.Wait(300) + OpenTVMenu() +end + +function OpenTVMenu() + local screen = CURRENT_SCREEN + if not screen then + return + end + lib.hideMenu() + local ChannelList = GetChannelList() + lib.registerMenu({ + id = "ptelevision-menu", + title = "Television", + position = "top-right", + onSideScroll = function(selected, scrollIndex, args) + if selected == 3 then + SetChannel(ChannelList.list[scrollIndex].index) + end + end, + onSelected = function(selected, scrollIndex, args) end, + onClose = function(keyPressed) end, + options = { + { label = "Videos", description = "Play a video or stream on the screen." }, + { label = "Web Browser", description = "Access the web via your TV." }, + { label = "TV Channels", values = ChannelList.display, description = "Live TV Channels in San Andreas!", defaultIndex = ChannelList.current }, + { label = "Interact With Screen", description = "Allows you to control on-screen elements." }, + { label = "Set Volume", description = "Sets your TV's volume (For yourself)." }, + { label = "Close Menu", close = true }, + }, + }, function(selected, scrollIndex, args) + if selected == 1 then + VideoMenu() + elseif selected == 2 then + WebBrowserMenu() + elseif selected == 3 then + SetChannel(ChannelList.list[scrollIndex].index) + OpenTVMenu() + elseif selected == 4 then + SetInteractScreen(true) + elseif selected == 5 then + VolumeMenu() + end + end) + lib.showMenu("ptelevision-menu") +end + +function PlayBrowser(data) + while not IsDuiAvailable(duiObj) do + Wait(10) + end + setDuiURL(data.url) +end + +function PlayVideo(data) + while not IsDuiAvailable(duiObj) do + Wait(10) + end + if getDuiURL() ~= DEFAULT_URL then + waitForLoad = true + setDuiURL(DEFAULT_URL) + while waitForLoad do + Wait(10) + end + end + SendDuiMessage( + duiObj, + json.encode({ + setVideo = true, + data = data, + }) + ) +end + +function ResetDisplay() + setDuiURL(DEFAULT_URL) +end + +function GetTelevisionLocal(coords) + for k, v in pairs(TelevisionsLocal) do + if #(v3(v.coords) - v3(coords)) < 0.01 then + return k, v + end + end +end + +function SetTelevisionLocal(coords, key, value) + local index, data = GetTelevisionLocal(coords) + if index ~= nil then + if TelevisionsLocal[index] == nil then + TelevisionsLocal[index] = {} + end + TelevisionsLocal[index][key] = value + else + index = GetGameTimer() + while TelevisionsLocal[index] do + index = index + 1 + Citizen.Wait(0) + end + if TelevisionsLocal[index] == nil then + TelevisionsLocal[index] = {} + end + TelevisionsLocal[index][key] = value + end + TelevisionsLocal[index].coords = coords + return index +end + +RegisterNetEvent("ptelevision:event", function(data, index, key, value) + Televisions = data + local data = Televisions[index] + local screen = CURRENT_SCREEN + if screen and #(v3(screen.coords) - v3(data.coords)) < 0.001 then + local index, data = GetTelevision(screen.coords) + if index then + local event = value + if event.type == "play" then + local data = { url = event.url } + if event.channel then + data = Channels[event.channel] + data.channel = event.channel + end + PlayVideo(data) + elseif event.type == "browser" then + PlayBrowser({ url = event.url }) + end + end + end + SetTelevisionLocal(Televisions[index].coords, "start_time", GetGameTimer()) +end) + +RegisterNetEvent("ptelevision:broadcast", function(data, index) + Channels = data + if getDuiURL() == DEFAULT_URL then + local screen = CURRENT_SCREEN + local tvObj = screen.entity + local _, status = GetTelevision(screen.coords) + if status and status.channel == index and data[index] == nil then + ResetDisplay() + Citizen.Wait(10) + end + SendDuiMessage( + duiObj, + json.encode({ + showNotification = true, + channel = index, + data = data[index], + }) + ) + end +end) + +RegisterCommand("tv", function() + OpenTVMenu() +end) + +RegisterCommand("broadcast", function(source, args, raw) + BroadcastMenu() +end) diff --git a/server-data/resources/[esx_addons]/ptelevision/config.lua b/server-data/resources/[esx_addons]/ptelevision/config.lua new file mode 100644 index 000000000..97c36a96f --- /dev/null +++ b/server-data/resources/[esx_addons]/ptelevision/config.lua @@ -0,0 +1,154 @@ +Config = {} + +Config.Models = { -- Any TV Models used on the map or in locations must be defined here. + [`des_tvsmash_start`] = { + DefaultVolume = 0.5, + Range = 20.0, + Target = "tvscreen", -- Only use if prop has render-target name. + Scale = 0.085, + Offset = vector3(-1.02, -0.055, 1.04), + }, + [`prop_flatscreen_overlay`] = { + DefaultVolume = 0.5, + Range = 20.0, + Target = "tvscreen", -- Only use if prop has render-target name. + Scale = 0.085, + Offset = vector3(-1.02, -0.055, 1.04), + }, + [`prop_laptop_lester2`] = { + DefaultVolume = 0.5, + Range = 20.0, + Target = "tvscreen", -- Only use if prop has render-target name. + Scale = 0.085, + Offset = vector3(-1.02, -0.055, 1.04), + }, + [`prop_monitor_02`] = { + DefaultVolume = 0.5, + Range = 20.0, + Target = "tvscreen", -- Only use if prop has render-target name. + Scale = 0.085, + Offset = vector3(-1.02, -0.055, 1.04), + }, + [`prop_trev_tv_01`] = { + DefaultVolume = 0.5, + Range = 20.0, + Target = "tvscreen", -- Only use if prop has render-target name. + Scale = 0.085, + Offset = vector3(-1.02, -0.055, 1.04), + }, + [`prop_tv_02`] = { + DefaultVolume = 0.5, + Range = 20.0, + Target = "tvscreen", -- Only use if prop has render-target name. + Scale = 0.085, + Offset = vector3(-1.02, -0.055, 1.04), + }, + [`prop_tv_03_overlay`] = { + DefaultVolume = 0.5, + Range = 20.0, + Target = "tvscreen", -- Only use if prop has render-target name. + Scale = 0.085, + Offset = vector3(-1.02, -0.055, 1.04), + }, + [`prop_tv_06`] = { + DefaultVolume = 0.5, + Range = 20.0, + Target = "tvscreen", -- Only use if prop has render-target name. + Scale = 0.085, + Offset = vector3(-1.02, -0.055, 1.04), + }, + [`prop_tv_flat_01`] = { + DefaultVolume = 0.5, + Range = 20.0, + Target = "tvscreen", -- Only use if prop has render-target name. + Scale = 0.085, + Offset = vector3(-1.02, -0.055, 1.04), + }, + [`prop_tv_flat_01_screen`] = { + DefaultVolume = 0.5, + Range = 20.0, + Target = "tvscreen", -- Only use if prop has render-target name. + Scale = 0.085, + Offset = vector3(-1.02, -0.055, 1.04), + }, + [`prop_tv_flat_02b`] = { + DefaultVolume = 0.5, + Range = 20.0, + Target = "tvscreen", -- Only use if prop has render-target name. + Scale = 0.085, + Offset = vector3(-1.02, -0.055, 1.04), + }, + [`prop_tv_flat_03`] = { + DefaultVolume = 0.5, + Range = 20.0, + Target = "tvscreen", -- Only use if prop has render-target name. + Scale = 0.085, + Offset = vector3(-1.02, -0.055, 1.04), + }, + [`prop_tv_flat_03b`] = { + DefaultVolume = 0.5, + Range = 20.0, + Target = "tvscreen", -- Only use if prop has render-target name. + Scale = 0.085, + Offset = vector3(-1.02, -0.055, 1.04), + }, + [`prop_tv_flat_michael`] = { + DefaultVolume = 0.5, + Range = 20.0, + Target = "tvscreen", -- Only use if prop has render-target name. + Scale = 0.085, + Offset = vector3(-1.02, -0.055, 1.04), + }, + [`prop_monitor_w_large`] = { + DefaultVolume = 0.5, + Range = 20.0, + Target = "tvscreen", -- Only use if prop has render-target name. + Scale = 0.085, + Offset = vector3(-1.02, -0.055, 1.04), + }, + [`prop_tv_03`] = { + DefaultVolume = 0.5, + Range = 20.0, + Target = "tvscreen", -- Only use if prop has render-target name. + Scale = 0.085, + Offset = vector3(-1.02, -0.055, 1.04), + }, + [`prop_tv_flat_02`] = { + DefaultVolume = 0.5, + Range = 20.0, + Target = "tvscreen", -- Only use if prop has render-target name. + Scale = 0.085, + Offset = vector3(-1.02, -0.055, 1.04), + }, +} + +Config.Locations = { -- REMOVE ALL IF NOT USING ONESYNC, OR IT SHALL BREAK. + { + Model = `prop_tv_flat_01`, + Position = vector4(144.3038, -1037.4647, 29.4173, 70.81), + }, +} + +Config.Channels = { -- These channels are default channels and cannot be overriden. + { name = "Twitch", url = "twitch.tv/twitch" }, +} + +Config.BannedWords = { + "google", +} + +Config.Events = { -- Events for approving broadcasts / interactions (due to popular demand). + ScreenInteract = function(source, data, key, value, cb) -- cb() to approve. + if value.url then + for i = 1, #Config.BannedWords do + if string.find(value.url, Config.BannedWords[i]) then + return + end + end + end + cb() + end, + Broadcast = function(source, data, cb) -- cb() to approve. + cb() + end, +} diff --git a/server-data/resources/[esx_addons]/ptelevision/fxmanifest.lua b/server-data/resources/[esx_addons]/ptelevision/fxmanifest.lua new file mode 100644 index 000000000..8bfc3d884 --- /dev/null +++ b/server-data/resources/[esx_addons]/ptelevision/fxmanifest.lua @@ -0,0 +1,32 @@ +fx_version("cerulean") +game("gta5") +author("Pickle Mods#0001") +version("v1.2.5") +ui_page("html/blank.html") + +files({ + "html/blank.html", + "html/index.html", + "html/style.css", + "html/main.js", + "html/VCR_OSD_MONO_1.001.ttf", +}) + +shared_scripts({ + "@ox_lib/init.lua", + "config.lua", + "shared/*.lua", +}) + +client_scripts({ + "client/cursor.lua", + "client/tv.lua", + "client/dui.lua", + "client/main.lua", +}) + +server_scripts({ + "server/*.lua", +}) + +lua54("yes") diff --git a/server-data/resources/[esx_addons]/ptelevision/html/VCR_OSD_MONO_1.001.ttf b/server-data/resources/[esx_addons]/ptelevision/html/VCR_OSD_MONO_1.001.ttf new file mode 100644 index 000000000..dcca687a4 Binary files /dev/null and b/server-data/resources/[esx_addons]/ptelevision/html/VCR_OSD_MONO_1.001.ttf differ diff --git a/server-data/resources/[esx_addons]/ptelevision/html/blank.html b/server-data/resources/[esx_addons]/ptelevision/html/blank.html new file mode 100644 index 000000000..4f830fa74 --- /dev/null +++ b/server-data/resources/[esx_addons]/ptelevision/html/blank.html @@ -0,0 +1,12 @@ + + + + + + + Document + + + + + \ No newline at end of file diff --git a/server-data/resources/[esx_addons]/ptelevision/html/index.html b/server-data/resources/[esx_addons]/ptelevision/html/index.html new file mode 100644 index 000000000..4cd66f808 --- /dev/null +++ b/server-data/resources/[esx_addons]/ptelevision/html/index.html @@ -0,0 +1,27 @@ + + + + + + + Document + + + + + + + + +
+
+
+
NO SIGNAL
+
+
+ +
+ + + + \ No newline at end of file diff --git a/server-data/resources/[esx_addons]/ptelevision/html/main.js b/server-data/resources/[esx_addons]/ptelevision/html/main.js new file mode 100644 index 000000000..33ef02108 --- /dev/null +++ b/server-data/resources/[esx_addons]/ptelevision/html/main.js @@ -0,0 +1,143 @@ +let player; +let playerData; +$(document).ready(function() { + $.post('https://ptelevision/pageLoaded', JSON.stringify({})); +}); + +function GetURLID(link) { + if (link == null) return; + const url = link.toString(); + const regExp = /^.*(youtu\.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/; + const match = url.match(regExp); + if (match && match[2].length == 11) { + return { type: 'youtube', id: match[2] }; + } + else if (url.split('twitch.tv/').length > 1) { + + return { type: 'twitch', id: url.split('twitch.tv/')[1] }; + } +} + +function ChannelDisplay(channel, channelFound) { + if (channel) { + let temp = 'CH '; + if (channel > 9) { + temp += channel; + } + else { + temp += ('0' + channel); + } + $('#overlay span').show(); + $('#overlay span').html(temp); + } + else { + $('#overlay span').show(); + $('#overlay span').html(''); + } + if (channelFound) { + $('#tv-container').hide(); + } + else { + $('#tv-container').show(); + } +} + +function SetVideo(video_data) { + const url = video_data.url; + const channel = video_data.channel; + const data = GetURLID(url); + + playerData = data; + if (player) { + player.destroy(); + player = null; + } + if (data) { + if (data.type == 'youtube') { + player = new YT.Player('twitch-embed', { + height: '100%', + width: '100%', + videoId: data.id, + playerVars: { + 'playsinline': 1, + }, + events: { + 'onReady': function(event) { + event.target.playVideo(); + event.target.seekTo(video_data.time); + }, + 'onStateChange': function(event) { + if (event.data == YT.PlayerState.PLAYING) { + event.target.unMute(); + } + // eslint-disable-next-line no-inline-comments + else if (event.data == YT.PlayerState.PAUSED) { /* empty */ } + }, + }, + }); + } + else if (data.type == 'twitch') { + player = new Twitch.Player('twitch-embed', { + width: '100%', + height: '100%', + channel: data.id, + volume: 1.0, + }); + player.addEventListener(Twitch.Embed.VIDEO_READY, function() { + player.setMuted(false); + }); + } + + $('#overlay span').hide(); + $('#tv-container').hide(); + } + if (channel) { + ChannelDisplay(channel, url); + } +} + +function SetVolume(volume) { + + if (player && playerData && player.setVolume) { + if (playerData.type == 'twitch') { + player.setMuted(false); + player.setVolume(volume / 100.0); + } + else if (playerData.type == 'youtube') { + player.unMute(); + player.setVolume(volume); + } + } +} + +function ShowNotification(channel, data) { + $('#tv-container').addClass('notify'); + $('#tv-container div').addClass('notify'); + const display = $('#tv-container').is(':visible'); + $('#tv-container').show(); + $('#tv-container div').html('Channel #' + channel + (data ? (' (' + data.name + ')') : '') + ' is now ' + (data ? 'live!' : 'offline.')); + + setTimeout(function() { + $('#tv-container').removeClass('notify'); + $('#tv-container div').removeClass('notify'); + $('#tv-container div').html('NO SIGNAL'); + if (!display) { + $('#tv-container').hide(); + } + }, 3500); +} + +window.addEventListener('message', function(ev) { + if (ev.data.setVideo) { + SetVideo(ev.data.data); + } + else if (ev.data.setVolume) { + SetVolume(ev.data.data); + } + else if (ev.data.showNotification) { + ShowNotification(ev.data.channel, ev.data.data); + } +}); +$(document).ready(function() { + ChannelDisplay(); +}); diff --git a/server-data/resources/[esx_addons]/ptelevision/html/style.css b/server-data/resources/[esx_addons]/ptelevision/html/style.css new file mode 100644 index 000000000..e2db007e3 --- /dev/null +++ b/server-data/resources/[esx_addons]/ptelevision/html/style.css @@ -0,0 +1,93 @@ +@font-face { + font-family: 'BodyCam'; + src: url('VCR_OSD_MONO_1.001.ttf') format('truetype') /* Safari, Android, iOS */ +} + +* { + font-family: BodyCam; + color:white; +} + +#twitch-embed { + position: absolute; + width: 100%; + height: 100%; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + overflow: hidden; + z-index: -1; +} + +#overlay { + position: absolute; + top: 10px; + right: 10px; + z-index: 1; + font-size: 48pt !important; +} + +#background { + display: flex; + position: absolute; + top: 0; + left: 0; + width: 100%; + min-height: 100%; + background-color: rgb(42, 42, 42); + justify-content: center; + align-items: center; + flex-wrap: wrap; + z-index: -2; +} + +#tv-container { + display: flex; + position: absolute; + top: 0; + left: 0; + width: 100%; + min-height: 100%; + background-color: transparent; + justify-content: center; + align-items: center; + flex-wrap: wrap; +} + +#tv-container.notify { + display: block; + position: absolute; + top: 0; + left: 0; + width: 100%; + min-height: 100%; +} + +#tv-container > div { + font-size: 24pt; + user-select: none; +} + +#tv-container > div.notify { + font-size: 24pt; + user-select: none; + width: -webkit-fill-available; + text-align: center; + padding: 25px; + background-color: rgba(0, 0, 0, 0.3); +} + +/* width */ +::-webkit-scrollbar { + width: 10px; +} + +/* Track */ +::-webkit-scrollbar-track { + background: rgb(42, 42, 42); +} + +/* Handle */ +::-webkit-scrollbar-thumb { + background: rgb(66, 66, 66); +} \ No newline at end of file diff --git a/server-data/resources/[esx_addons]/ptelevision/server/main.lua b/server-data/resources/[esx_addons]/ptelevision/server/main.lua new file mode 100644 index 000000000..b39560fcc --- /dev/null +++ b/server-data/resources/[esx_addons]/ptelevision/server/main.lua @@ -0,0 +1,87 @@ +local Locations = {} + +function SetTelevision(coords, key, value, update) + local index, data = GetTelevision(coords) + if index ~= nil then + if Televisions[index] == nil then + Televisions[index] = {} + end + Televisions[index][key] = value + else + index = os.time() + while Televisions[index] do + index = index + 1 + Citizen.Wait(0) + end + if Televisions[index] == nil then + Televisions[index] = {} + end + Televisions[index][key] = value + end + Televisions[index].coords = coords + Televisions[index].update_time = os.time() + if update then + TriggerClientEvent("ptelevision:event", -1, Televisions, index, key, value) + end + return index +end + +function SetChannel(source, data) + if data then + for k, v in pairs(Channels) do + if Channels[k].source == source then + return + end + end + local index = 1 + while Channels[index] do + index = index + 1 + Citizen.Wait(0) + end + Channels[index] = data + Channels[index].source = source + TriggerClientEvent("ptelevision:broadcast", -1, Channels, index) + return + else + for k, v in pairs(Channels) do + if Channels[k].source == source then + Channels[k] = nil + TriggerClientEvent("ptelevision:broadcast", -1, Channels, k) + return + end + end + end +end + +RegisterNetEvent("ptelevision:requestSync", function(coords) + local _source = source + local index, data = GetTelevision(coords) + TriggerClientEvent("ptelevision:requestSync", _source, coords, { current_time = os.time() }) +end) + +RegisterNetEvent("ptelevision:event", function(data, key, value) + local _source = source + Config.Events.ScreenInteract(_source, data, key, value, function() + SetTelevision(data.coords, key, value, true) + end) +end) + +RegisterNetEvent("ptelevision:broadcast", function(data) + local _source = source + Config.Events.Broadcast(_source, data, function() + SetChannel(_source, data) + end) +end) + +RegisterNetEvent("ptelevision:requestUpdate", function() + local _source = source + TriggerClientEvent("ptelevision:requestUpdate", _source, { + Televisions = Televisions, + Channels = Channels, + }) +end) + +AddEventHandler("playerDropped", function(reason) + local _source = source + SetChannel(_source, nil) +end) diff --git a/server-data/resources/[esx_addons]/ptelevision/shared/main.lua b/server-data/resources/[esx_addons]/ptelevision/shared/main.lua new file mode 100644 index 000000000..8ce8df89a --- /dev/null +++ b/server-data/resources/[esx_addons]/ptelevision/shared/main.lua @@ -0,0 +1,31 @@ +Televisions = {} + +function v3(coord) + return vector3(coord.x, coord.y, coord.z), coord.w +end + +function DumpArray(obj, seen) + if type(obj) ~= "table" then + return obj + end + if seen and seen[obj] then + return seen[obj] + end + local s = seen or {} + local res = setmetatable({}, getmetatable(obj)) + s[obj] = res + for k, v in pairs(obj) do + res[DumpArray(k, s)] = DumpArray(v, s) + end + return res +end + +function GetTelevision(coords) + for k, v in pairs(Televisions) do + if #(v3(v.coords) - v3(coords)) < 0.01 then + return k, v + end + end +end + +Channels = DumpArray(Config.Channels)