Config = {}
Config.Framework = {
["Framework"] = "auto", -- [[LEAVE AUTO]] auto, esx, qbcore or qbox
["ResourceName"] = "auto", -- [[LEAVE AUTO]] auto, es_extended or qb-core or your resource name. If you using qbx you should write qb-core
["SharedEvent"] = "" -- Event name for old cores.
}
Config.Language = "en" -- ar, cz, ro, it, fr, de, tr
Config.ShowDebug = false -- Show debug messages
Config.AdminMenu = {
["command"] = "tvadmin", -- Command to open the admin menu
["ace"] = "group.admin" -- ACE permission for the admin menu
}
Config.TvCommand = {
["command"] = "tv", -- This allows you to open the menu on your TV.
["suggestion"] = "This allows you to open the menu on the nearby TV" -- Command suggestion for the tv command
}
Config.DeleteTvWhenOwnerQuit = false --[[
⚠️⚠️⚠️
Its recommended to keep it true, if you set it to false, when the owner of the TV leaves the game,
the TV will remain in the world and other players can use it.
If you set it to true, when the owner of the TV leaves the game,
the TV will be deleted from the world.
]]
Config.SyncCheck = true --[[
⚠️⚠️⚠️
This variable belongs to the code structure that checks whether the video is progressing as it should, in sync with other people.
The reasons I use this are as follows:
- When the game is minimized, the CPU freezes all game-related processes for a certain number of seconds or minutes.
> This suspends the game's processes. When the player returns to the game, they will hear the videos behind schedule by the duration of the freeze compared to other players.
- Players experiencing occasional freezes or lag will hear the video delayed by the duration of the game's lag.
This way, the remaining users will move back to the current second.
💢💢💢
If you set it to `false`, when these conditions occur, the user's video will lag behind the others (by the amount of time it freezes or stutters).
Therefore, it will not be synchronized, and when the video ends at the expected second, it will suddenly cut off.
]]
Config.UsableItems = {
["tv_crt_classic"] = {
["model"] = "prop_tv_01",
["screen"] = "tvscreen",
["label"] = "Classic CRT TV"
},
["tv_crt_old"] = {
["model"] = "prop_tv_03",
["screen"] = "tvscreen",
["label"] = "Old CRT TV"
},
["tv_crt_wood"] = {
["model"] = "prop_tv_06",
["screen"] = "tvscreen",
["label"] = "Wooden CRT TV"
},
["tv_flat_small"] = {
["model"] = "prop_tv_flat_03b",
["screen"] = "tvscreen",
["label"] = "Small Flat TV"
},
["tv_cinema"] = {
["model"] = "vw_prop_vw_cinema_tv_01",
["screen"] = "tvscreen",
["label"] = "Cinema TV"
},
["tv_curved_xl"] = {
["model"] = "xm_prop_x17_screens_02a",
["screen"] = "tvscreen",
["label"] = "Curved XL TV"
},
["tv_smash"] = {
["model"] = "des_tvsmash_start",
["screen"] = "tvscreen",
["label"] = "Smash TV"
},
["tv_flat_overlay"] = {
["model"] = "prop_flatscreen_overlay",
["screen"] = "tvscreen",
["label"] = "Flat Overlay TV"
},
["tv_laptop"] = {
["model"] = "prop_laptop_lester2",
["screen"] = "tvscreen",
["label"] = "Laptop TV"
},
["tv_trevor"] = {
["model"] = "prop_trev_tv_01",
["screen"] = "tvscreen",
["label"] = "Trevor TV"
},
["tv_vintage"] = {
["model"] = "prop_tv_02",
["screen"] = "tvscreen",
["label"] = "Vintage TV"
},
["tv_crt_overlay"] = {
["model"] = "prop_tv_03_overlay",
["screen"] = "tvscreen",
["label"] = "CRT Overlay TV"
},
["tv_flat_classic"] = {
["model"] = "prop_tv_flat_01",
["screen"] = "tvscreen",
["label"] = "Flat Classic TV"
},
["tv_flat_screen"] = {
["model"] = "prop_tv_flat_01_screen",
["screen"] = "tvscreen",
["job"] = {"police", "ambulance"},
["label"] = "Flat Screen TV"
},
["tv_flat_medium"] = {
["model"] = "prop_tv_flat_02b",
["screen"] = "tvscreen",
["label"] = "Flat Medium TV"
},
["tv_flat_modern"] = {
["model"] = "prop_tv_flat_03",
["screen"] = "tvscreen",
["label"] = "Flat Modern TV"
},
["tv_flat_premium"] = {
["model"] = "prop_tv_flat_michael",
["screen"] = "tvscreen",
["label"] = "Flat Premium TV"
},
["tv_monitor_large"] = {
["model"] = "prop_monitor_w_large",
["screen"] = "tvscreen",
["label"] = "Large Monitor TV"
},
}
Config.PlaceOptions = {
["timeout"] = 15 * 1000, -- Time to wait for the player to place the TV
["maxDistance"] = 15.0, -- Max distance to place the TV from the player
["buttons"] = {
["accept"] = "E", -- Button to accept placing the TV
["cancel"] = "G", -- Button to cancel placing the TV
["snapGround"] = "Z" -- Button to snap the TV to the ground.
},
["offset"] = vector4(1.25, 0.0, 0.0, 0.0), -- Offset from the player when placing the TV (x, y, z, heading)
["animations"] = {
["dict"] = "anim@heists@load_box", -- Animation dictionary for placing the TV
["clip"] = "load_box_1", -- Animation clip for placing the TV
["flag"] = 1, -- Animation flag for placing the TV
["duration"] = 2.5 * 1000, -- Duration of the animation in milliseconds
},
}
Config.BlacklistedCoords = {
--[[
🚫🚫🚫
You can add blacklist coordinates so that people cannot place tv's or screens there.
Example: { coords = vector3(x, y, z), radius = 10.0 }
]]
{ coords = vector3(455.5057, -994.2595, 26.9876), radius = 45.0 }, -- Mission Row Police Department [MRPD]
{ coords = vector3(320.7686, -590.9871, 45.0281), radius = 40.0 } -- Pillbox Hill Medical Center [PHMC]
}
Config.Debug = function(...)
if Config.ShowDebug then
print(...)
end
end
Config.Notification = function(title, text, type, time)
title = title or "Television"
time = time or 5000
return lib.notify({
title = title,
type = type,
duration = time,
description = text,
iconAnimation = "beatFade",
position = "top"
})
end
ConfigSv = {}
ConfigSv.Webhook = "" -- Webhook URL for logging when a song starts playing. Set this to your Discord webhook URL to enable logging.
ConfigSv.Inventorys = {
-- [[ 🟢 Inventory detections, to work automatically compatible with some inventories. 🟢 ]]
["qb_inventory"] = GetResourceState("qb-inventory"):find("start") and true or false,
["qs_inventory"] = GetResourceState("qs-inventory"):find("start") and true or false,
["ox_inventory"] = GetResourceState("ox_inventory"):find("start") and true or false,
["export"] = nil
}
---------------------------------------------------------------------------------------------------------------
---This function is used to access player names.
---@enum [parent=#ConfigSv] GetPlayerName
---@param source
---@return #string
ConfigSv.GetPlayerName = function(source)
if Config.Framework.Framework == "esx" then
local Player = wFramework.Framework.GetPlayerFromId(source)
return Player ~= nil and ('%s - %s'):format(Player.get('firstName'), Player.get('lastName')) or "Unknown Player"
elseif Config.Framework.Framework:find("qb") then
local Player = wFramework.Framework.Functions.GetPlayer(source)
return Player ~= nil and ('%s - %s'):format(Player.PlayerData.charinfo.firstname, Player.PlayerData.charinfo.lastname) or "Unknown Player"
end
end
---------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------
---Function that takes care of player items giving
---@enum [parent=#ConfigSv] AddItem
---@param source
---@param item
---@param amount
---@return #boolean
ConfigSv.AddItem = function(src, item, amount)
local p = promise:new()
if Config.Framework.Framework == "esx" then
local xPlayer = wFramework.Framework.GetPlayerFromId(src)
if xPlayer ~= nil then
xPlayer.addInventoryItem(item, amount)
p:resolve(true)
else
Config.Debug(("[^1ERROR - ADDITEM^0] Could not find player with source %s - ConfigSv.AddItem"):format(src))
p:resolve(false)
end
elseif Config.Framework.Framework:find("qb") then
local Player = wFramework.Framework.Functions.GetPlayer(src)
if Player ~= nil then
if (ConfigSv.Inventorys.export == nil or ConfigSv.Inventorys.export:AddItem(src, item, amount) == nil) then
Player.Functions.AddItem(item, amount)
end
p:resolve(true)
else
Config.Debug(("[^1ERROR - ADDITEM^0] Could not find player with source %s - ConfigSv.AddItem"):format(src))
p:resolve(false)
end
else
Config.Debug(("[^1ERROR - ADDITEM^0] No compatible inventory found for adding item to player with source %s - ConfigSv.AddItem"):format(src))
p:resolve(false)
end
return Citizen.Await(p)
end
---------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------
---Function that takes care of player items removing
---@enum [parent=#ConfigSv] RemoveItem
---@param source
---@param item
---@param amount
---@return #boolean
ConfigSv.RemoveItem = function(src, item, amount)
local p = promise:new()
local Player = Config.Framework.Framework == "esx" and wFramework.Framework.GetPlayerFromId(src) or ((Config.Framework.Framework:find("qb")) and wFramework.Framework.Functions.GetPlayer(src) or nil)
if not Player then
p:resolve(false)
Config.Debug(("[^1ERROR - REMOVEITEM^0] Could not find player with source %s - ConfigSv.RemoveItem"):format(src))
return Citizen.Await(p)
end
if ConfigSv.Inventorys.export ~= nil then
p:resolve(ConfigSv.Inventorys.export:RemoveItem(src, item, amount))
return Citizen.Await(p)
end
if Config.Framework.Framework:find("qb") then
p:resolve(Player.Functions.RemoveItem(item, amount))
else
if Player.getInventoryItem(item).count >= amount then
Player.removeInventoryItem(item, amount)
p:resolve(true)
else
p:resolve(false)
end
end
return Citizen.Await(p)
end
---------------------------------------------------------------------------------------------------------------
---Function to send a webhook when a song starts playing. It sends the owner's source, video ID, net ID, and metadata (which includes title, length, author, etc.). You can customize the data sent to the webhook as needed.
---@enum [parent=#ConfigSv] SendWebhook
---@param owner number The source of the player who started the song.
---@param videoId string The ID of the YouTube video being played.
---@param netId number The network ID of the television entity.
---@param metaData table A table containing metadata about the song, such as title, length, author, thumbnail, etc.
---@param platform string The platform of the video/stream (e.g., "youtube", "twitch").
---@param url string The URL of the video/stream.
---@return void
ConfigSv.SendWebhook = function(sender, coords, url, platform, metadata)
if ConfigSv.Webhook == "" then return end
--[[
🟢🟢🟢
You can set up a webhook to log when a song starts playing.
The function receives the owner's source, video ID, net ID, and metadata (which includes title, length, author, etc.).
You can customize the data sent to the webhook as needed.
]]
local ts = os.time()
local time = os.date('!%Y-%m-%dT%H:%M:%S.000Z', ts)
local name = sender > 0 and GetPlayerName(sender) or "Server DefaultTV"
local embed = {
{
["description"] = 'The player played a video/stream',
["color"] = 16705372,
["fields"] = {
{["name"] = "📃 Player", ["value"] = ('**%s [%s]**'):format(name, sender)},
{["name"] = "🎥 Video/Stream", ["value"] = ('**[%s-%s](%s)**'):format(metadata.author, metadata.title, CompleteURL(platform, url))},
{["name"] = "📍 Coords", ["value"] = ("vec3(%.2f, %.2f, %.2f)"):format(coords.x, coords.y, coords.z)},
},
["author"] = {
["name"] = "Television",
["url"] = "https://0resmon.tebex.io/package/fivem-speaker-boombox-carplay-script",
["icon_url"] = "https://cdn-icons-png.flaticon.com/32/4406/4406124.png"
},
["timestamp"] = time,
["thumbnail"] = {
["url"] = metadata.thumbnail
}
}
}
PerformHttpRequest(ConfigSv.Webhook, function(err, text, headers) end, 'POST', json.encode({username = "wais-tv", avatar_url = "https://cdn.discordapp.com/avatars/225154669817626626/12042ba04def3b74fdb3f6492853ee37.png?size=1024", embeds = embed}), { ['Content-Type'] = 'application/json' })
end
---------------------------------------------------------------------------------------------------------------
CreateThread(function()
ConfigSv.Inventorys.export = ConfigSv.Inventorys.qs_inventory and exports['qs-inventory'] or ConfigSv.Inventorys.ox_inventory and exports.ox_inventory or nil or ConfigSv.Inventorys.qb_inventory and exports['qb-inventory'] or nil
if not ConfigSv.Inventorys.export then
Config.Debug("No compatible inventory found, please set the export method manually in config_sv.lua")
end
end)