Moved all files to root.

This commit is contained in:
Pickle
2022-12-02 23:29:01 -05:00
committed by GitHub
parent ef83fba743
commit f70c477c8d
14 changed files with 1054 additions and 0 deletions

85
client/cursor.lua Normal file
View File

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

26
client/dui.lua Normal file
View File

@@ -0,0 +1,26 @@
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 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

223
client/main.lua Normal file
View File

@@ -0,0 +1,223 @@
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()
waitForLoad = false
end)
AddEventHandler('onResourceStop', function(name)
if name == GetCurrentResourceName() then
HideScreen()
for i=1, #Locations do
DeleteEntity(Locations[i].obj)
end
end
end)

226
client/tv.lua Normal file
View File

@@ -0,0 +1,226 @@
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)

32
client/utils.lua Normal file
View File

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

40
config.lua Normal file
View File

@@ -0,0 +1,40 @@
Config = {}
Config.Models = { -- Any TV Models used on the map or in locations must be defined here.
[`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)
}
}
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 = "Pickle Mods", url = "twitch.tv/picklemods"},
}
Config.BannedWords = {
"google",
}
Config.Events = { -- Events for approving broadcasts / interactions (due to popular demand).
ScreenInteract = function(source, data, key, value, cb) -- cb() to approve.
for i=1, #Config.BannedWords do
if string.find(value.url, Config.BannedWords) then
return
end
end
cb()
end,
Broadcast = function(source, data, cb) -- cb() to approve.
cb()
end,
}

33
fxmanifest.lua Normal file
View File

@@ -0,0 +1,33 @@
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/dui.lua",
"client/main.lua",
}
server_scripts {
"server/*.lua"
}
lua54 'yes'

BIN
html/VCR_OSD_MONO_1.001.ttf Normal file

Binary file not shown.

12
html/blank.html Normal file
View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body style="background-color: transparent">
</body>
</html>

27
html/index.html Normal file
View File

@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="style.css">
<script src="https://code.jquery.com/jquery-3.6.1.min.js" integrity="sha256-o88AwQnZB+VDvE9tvIXrMQaPlFFSUTR+nldQm1LuPXQ=" crossorigin="anonymous"></script>
<script src="https://player.twitch.tv/js/embed/v1.js"></script>
<script src="https://www.youtube.com/iframe_api"> </script>
<script src="main.js"></script>
</head>
<body>
<!-- Add a placeholder for the Twitch embed -->
<div id="background">
</div>
<div id="tv-container">
<div>NO SIGNAL</div>
</div>
<div id="overlay"><span></span></div>
<div id="twitch-embed"></div>
<!-- Load the Twitch embed script -->
</body>
</html>

144
html/main.js Normal file
View File

@@ -0,0 +1,144 @@
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<span style="font-size: 18pt !important;"> </span>'
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 && player.setVolume) {
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()
})

93
html/style.css Normal file
View File

@@ -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);
}

88
server/main.lua Normal file
View File

@@ -0,0 +1,88 @@
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)

25
shared/main.lua Normal file
View File

@@ -0,0 +1,25 @@
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)