diff --git a/generic_texture_renderer_gfx/__resource.lua b/generic_texture_renderer_gfx/__resource.lua new file mode 100644 index 0000000..7caf209 --- /dev/null +++ b/generic_texture_renderer_gfx/__resource.lua @@ -0,0 +1 @@ +resource_manifest_version '44febabe-d386-4d18-afbe-5e627f4af937' diff --git a/generic_texture_renderer_gfx/stream/generic_texture_renderer.gfx b/generic_texture_renderer_gfx/stream/generic_texture_renderer.gfx new file mode 100644 index 0000000..8151eff Binary files /dev/null and b/generic_texture_renderer_gfx/stream/generic_texture_renderer.gfx differ diff --git a/generic_texture_renderer_gfx/stream/generic_texture_renderer_2.gfx b/generic_texture_renderer_gfx/stream/generic_texture_renderer_2.gfx new file mode 100644 index 0000000..8151eff Binary files /dev/null and b/generic_texture_renderer_gfx/stream/generic_texture_renderer_2.gfx differ diff --git a/ptelevision/client/cursor.lua b/ptelevision/client/cursor.lua new file mode 100644 index 0000000..7f86cd8 --- /dev/null +++ b/ptelevision/client/cursor.lua @@ -0,0 +1,85 @@ +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/ptelevision/client/main.lua b/ptelevision/client/main.lua new file mode 100644 index 0000000..4286013 --- /dev/null +++ b/ptelevision/client/main.lua @@ -0,0 +1,158 @@ +DEFAULT_URL = "https://cfx-nui-ptelevision/html/index.html" +duiUrl = DEFAULT_URL +duiObj = nil +tvObj = nil + +function getDuiURL() + return duiUrl +end + +function GetVolume(dist, range) + local rem = (dist / range) + rem = rem > 1 and 1 or rem + return (100 - math.floor(rem * 100)) +end + +function setDuiURL(url) + duiUrl = url + SetDuiUrl(duiObj, duiUrl) +end + +local scale = 0.13 +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) + 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() + local tvObj = NetToObj(data.net_id) + local status = Entity(tvObj).state['ptv_status'] + local lstatus = Entity(tvObj).state['ptv_status_local'] + if status then + Citizen.Wait(1000) + if status.type == "play" and lstatus then + if (status.channel and GlobalState.Channels[status.channel]) then + PlayVideo({url = GlobalState.Channels[status.channel].url, channel = status.channel}) + elseif (status.url) then + PlayVideo({url = status.url, time = math.floor((GetGameTimer() - lstatus.start_time) / 1000)}) + end + elseif (status.type == "browser") then + PlayBrowser({ url = status.url }) + end + end + while duiObj do + if (tvObj and sfHandle ~= nil and HasScaleformMovieLoaded(sfHandle)) then + local pos = GetEntityCoords(tvObj) + local min, max = GetModelDimensions(GetEntityModel(tvObj)) + local scale = 0.085 + local offset = GetOffsetFromEntityInWorldCoords(tvObj, -1.02, -0.055, 1.04) + local hz = GetEntityHeading(tvObj) + DrawScaleformMovie_3dNonAdditive(sfHandle, offset.x, offset.y, offset.z, 0.0, -hz, 0.0, 2.0, 2.0, 2.0, scale * 1, scale * (9/16), 1, 2) + end + Citizen.Wait(0) + end + end) + Citizen.CreateThread(function() + local screen = GetClosestScreen() + local modelData = Config.Models[screen.model] + local coords = screen.coords + local range = modelData.Range + while duiObj do + local pcoords = GetEntityCoords(PlayerPedId()) + local dist = #(coords - pcoords) + SendDuiMessage(duiObj, json.encode({ + setVolume = true, + data = GetVolume(dist, range) + })) + Citizen.Wait(100) + end + end) +end + +function HideScreen() + 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, net_id = ObjToNet(entity)} + end + end + end + return (closest.entity and closest or nil) +end + +Citizen.CreateThread(function() + Citizen.Wait(2000) + while true do + local wait = 2500 + local data = GetClosestScreen() + if (data and not duiObj) then + ShowScreen(data) + elseif (not data and duiObj) then + HideScreen() + end + Citizen.Wait(wait) + end +end) + +RegisterNUICallback("pageLoaded", function() + waitForLoad = false +end) + +AddEventHandler('onResourceStop', function(name) + if name == GetCurrentResourceName() then + HideScreen() + end +end) \ No newline at end of file diff --git a/ptelevision/client/tv.lua b/ptelevision/client/tv.lua new file mode 100644 index 0000000..df43e99 --- /dev/null +++ b/ptelevision/client/tv.lua @@ -0,0 +1,174 @@ +function SetChannel(index) + TriggerServerEvent("ptelevision:event", GetClosestScreen().net_id, "ptv_status", { + type = "play", + channel = index, + }) +end + +function GetChannelList() + local channel_list = {} + local menu_list = {} + local current = 1 + local screen = GetClosestScreen() + local ent = NetToObj(screen.net_id) + local status = Entity(ent).state['ptv_status'] + local channel = nil + if (status) then + channel = status.channel + end + for index,value in pairs(GlobalState.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(GlobalState.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() + local input = lib.inputDialog('Web Browser', {'URL:'}) + + if not input then OpenTVMenu() end + TriggerServerEvent("ptelevision:event", GetClosestScreen().net_id, "ptv_status", { + type = "browser", + url = input[1] + }) + OpenTVMenu() +end + +function VideoMenu() + local input = lib.inputDialog('Video Player', {'URL:'}) + + if not input then OpenTVMenu() end + TriggerServerEvent("ptelevision:event", GetClosestScreen().net_id, "ptv_status", { + type = "play", + url = input[1] + }) + OpenTVMenu() +end + +function OpenTVMenu() + local screen = GetClosestScreen() + 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 = '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 + + 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 + +AddStateBagChangeHandler("ptv_status", nil, function(bagName, key, value, reserved, replicated) + local net_id = tonumber(bagName:gsub('entity:', ''), 10) + local ent = NetToObj(net_id) + local screen = GetClosestScreen() + if (screen and screen.net_id == net_id and DoesEntityExist(ent)) then + local event = value + if (event.type == "play") then + local data = { url = event.url } + if (event.channel) then + data = GlobalState.Channels[event.channel] + data.channel = event.channel + end + PlayVideo(data) + elseif (event.type == "browser") then + PlayBrowser({ url = event.url }) + end + end + Entity(ent).state:set('ptv_status_local', { + start_time = GetGameTimer() + }, false) +end) + +RegisterNetEvent("ptelevision:broadcast", function(index, data) + if getDuiURL() == DEFAULT_URL then + local screen = GetClosestScreen() + local tvObj = NetToObj(screen.net_id) + local status = Entity(tvObj).state['ptv_status'] + if (status and status.channel == index and data == nil) then + ResetDisplay() + Citizen.Wait(10) + end + SendDuiMessage(duiObj, json.encode({ + showNotification = true, + channel = index, + data = data + })) + end +end) + +RegisterCommand('tv', function() + OpenTVMenu() +end) + +RegisterCommand("broadcast", function(source, args, raw) + BroadcastMenu() +end) \ No newline at end of file diff --git a/ptelevision/client/utils.lua b/ptelevision/client/utils.lua new file mode 100644 index 0000000..5cb43a9 --- /dev/null +++ b/ptelevision/client/utils.lua @@ -0,0 +1,32 @@ +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 \ No newline at end of file diff --git a/ptelevision/config.lua b/ptelevision/config.lua new file mode 100644 index 0000000..4a52110 --- /dev/null +++ b/ptelevision/config.lua @@ -0,0 +1,23 @@ +Config = {} +Config.Models = { + [`prop_tv_flat_01`] = { + Range = 20.0, + Scale = 0.085, + Offset = vector3(-1.02, -0.055, 1.04), + } +} + +Config.Locations = { + { + Model = `prop_tv_flat_01`, + Position = vector4(144.3038, -1037.4647, 29.4173, 70.1882) + }, + { + Model = `prop_tv_flat_01`, + Position = vector4(264.0882, -830.7057, 29.4569, 340.7550) + }, +} + +Config.Channels = { -- These channels are default channels and cannot be overriden. + {name = "Pickle Mods", url = "twitch.tv/picklemods"}, +} \ No newline at end of file diff --git a/ptelevision/fxmanifest.lua b/ptelevision/fxmanifest.lua new file mode 100644 index 0000000..270c4ef --- /dev/null +++ b/ptelevision/fxmanifest.lua @@ -0,0 +1,32 @@ +fx_version "cerulean" +game "gta5" +author "Pickle Mods#0001" + +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/utils.lua", + "client/tv.lua", + "client/main.lua", +} + +server_scripts { + "server/*.lua" +} + +lua54 'yes' \ No newline at end of file diff --git a/ptelevision/html/VCR_OSD_MONO_1.001.ttf b/ptelevision/html/VCR_OSD_MONO_1.001.ttf new file mode 100644 index 0000000..dcca687 Binary files /dev/null and b/ptelevision/html/VCR_OSD_MONO_1.001.ttf differ diff --git a/ptelevision/html/blank.html b/ptelevision/html/blank.html new file mode 100644 index 0000000..c244d68 --- /dev/null +++ b/ptelevision/html/blank.html @@ -0,0 +1,12 @@ + + +
+ + + +