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 @@ + + + + + + + Document + + + + + \ No newline at end of file diff --git a/ptelevision/html/index.html b/ptelevision/html/index.html new file mode 100644 index 0000000..407f232 --- /dev/null +++ b/ptelevision/html/index.html @@ -0,0 +1,27 @@ + + + + + + + Document + + + + + + + + +
+
+
+
NO SIGNAL
+
+
+ +
+ + + + \ No newline at end of file diff --git a/ptelevision/html/main.js b/ptelevision/html/main.js new file mode 100644 index 0000000..f1be8f6 --- /dev/null +++ b/ptelevision/html/main.js @@ -0,0 +1,143 @@ +var player; +var playerData; +$(document).ready(function() { + $.post("https://ptelevision/pageLoaded", JSON.stringify({})) +}) + +function GetURLID(link) { + if (link == null) return; + let url = link.toString(); + var regExp = /^.*(youtu\.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/; + var 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) { + var 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) { + var url = video_data.url; + var channel = video_data.channel; + var 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(); + } + else if (event.data == YT.PlayerState.PAUSED) { + + } + } + } + }); + } + 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) { + if (playerData.type == "twitch") { + player.setMuted(false); + player.setVolume(volume / 100.0); + } + else if (playerData.type == "youtube") { + player.setVolume(volume); + player.unMute(); + } + } +} + +function ShowNotification(channel, data) { + $("#tv-container").addClass("notify") + $("#tv-container div").addClass("notify") + var 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() +}) \ No newline at end of file diff --git a/ptelevision/html/style.css b/ptelevision/html/style.css new file mode 100644 index 0000000..d93ed21 --- /dev/null +++ b/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/ptelevision/server/main.lua b/ptelevision/server/main.lua new file mode 100644 index 0000000..c357655 --- /dev/null +++ b/ptelevision/server/main.lua @@ -0,0 +1,79 @@ +local Locations = {} + +GlobalState.Channels = {} +GlobalState.Channels = DumpArray(Config.Channels) + +RegisterNetEvent("ptelevision:event", function(net_id, key, value) + local ent = NetworkGetEntityFromNetworkId(net_id) + + if (DoesEntityExist(ent)) then + Entity(ent).state:set(key, value, true) + else + Entity(ent).state:set(key, value, true) + end +end) + +RegisterNetEvent("ptelevision:broadcast", function(data) + local _source = source + local broadcasts = GlobalState.Channels + if data then + for k,v in pairs(broadcasts) do + if (broadcasts[k].source == _source) then + return + end + end + local index = 1 + while true do + if not (broadcasts[index]) then + broadcasts[index] = data + broadcasts[index].source = _source + TriggerClientEvent("ptelevision:broadcast", -1, index, broadcasts[index]) + break + end + index = index + 1 + Citizen.Wait(0) + end + else + for k,v in pairs(broadcasts) do + if (broadcasts[k].source == _source) then + broadcasts[k] = nil + TriggerClientEvent("ptelevision:broadcast", -1, k, broadcasts[k]) + break + end + end + end + GlobalState.Channels = broadcasts +end) + +Citizen.CreateThread(function() + Citizen.Wait(1000) + local locations = Config.Locations + for i=1, #locations do + local data = locations[i] + local obj = CreateObject(data.Model, data.Position.x, data.Position.y, data.Position.z, true) + SetEntityHeading(obj, data.Position.w) + table.insert(Locations, {data = data, obj = obj}) + end +end) + +AddEventHandler('onResourceStop', function(name) + if name == GetCurrentResourceName() then + for i=1, #Locations do + local data = Locations[i] + DeleteEntity(Locations[i].obj) + end + end +end) + +AddEventHandler('playerDropped', function(reason) + local source = _source + local broadcasts = GlobalState.Channels + for k,v in pairs(broadcasts) do + if (broadcasts[k].source == _source) then + broadcasts[k] = nil + TriggerClientEvent("ptelevision:broadcast", -1, k, broadcasts[k]) + break + end + end + GlobalState.Channels = broadcasts +end) \ No newline at end of file diff --git a/ptelevision/shared/main.lua b/ptelevision/shared/main.lua new file mode 100644 index 0000000..a8e075c --- /dev/null +++ b/ptelevision/shared/main.lua @@ -0,0 +1,9 @@ +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 \ No newline at end of file