forked from Simnation/Main
ed
This commit is contained in:
parent
4c44afc37c
commit
2e129ccb92
26 changed files with 3020 additions and 2 deletions
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,86 @@
|
|||
CreateThread(function()
|
||||
if Config.Dependency.UseTarget == 'qb' then
|
||||
for k,v in ipairs(Config.WatchLoc) do
|
||||
exports['qb-target']:AddCircleZone("spycam_watch"..k,v.coords,v.rad, {
|
||||
name = "spycam_watch"..k,
|
||||
useZ=true,
|
||||
debugPoly = v.debug,
|
||||
}, {
|
||||
options = {
|
||||
{
|
||||
type = "client",
|
||||
icon = "fas fa-sign-in-alt",
|
||||
label = 'Open Camera Portal',
|
||||
action = function(entity)
|
||||
TriggerEvent('spy-bodycam:openActiveMenu',k)
|
||||
end,
|
||||
canInteract = function(entity)
|
||||
return targetAuth(k,PlayerJob.name)
|
||||
end,
|
||||
|
||||
},
|
||||
{
|
||||
type = "client",
|
||||
icon = "fas fa-list",
|
||||
label = 'Records Database',
|
||||
action = function(entity)
|
||||
TriggerServerEvent('spy-bodycam:server:showrecordingUI')
|
||||
end,
|
||||
canInteract = function(entity)
|
||||
return targetAuth(k,PlayerJob.name)
|
||||
end,
|
||||
|
||||
},
|
||||
},
|
||||
distance = 2.5
|
||||
})
|
||||
end
|
||||
else
|
||||
for k,v in ipairs(Config.WatchLoc) do
|
||||
exports.ox_target:addSphereZone(
|
||||
{
|
||||
coords = v.coords,
|
||||
radius = v.rad,
|
||||
debug = v.debug,
|
||||
drawSprite = false,
|
||||
options = {
|
||||
{
|
||||
name = "spycam_watch"..k,
|
||||
label = 'Open Camera Portal',
|
||||
icon = "fas fa-sign-in-alt",
|
||||
distance = 2.5,
|
||||
onSelect = function(data)
|
||||
TriggerEvent('spy-bodycam:openActiveMenu',k)
|
||||
end,
|
||||
canInteract = function(entity, distance, coords, name, bone)
|
||||
return targetAuth(k,PlayerJob.name)
|
||||
end,
|
||||
},
|
||||
{
|
||||
name = "spycam_watch"..k,
|
||||
label = 'Records Database',
|
||||
icon = "fas fa-list",
|
||||
distance = 2.5,
|
||||
onSelect = function(data)
|
||||
TriggerServerEvent('spy-bodycam:server:showrecordingUI')
|
||||
end,
|
||||
canInteract = function(entity, distance, coords, name, bone)
|
||||
return targetAuth(k,PlayerJob.name)
|
||||
end,
|
||||
},
|
||||
}
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
AddEventHandler('onResourceStop', function(resourceName)
|
||||
if GetCurrentResourceName() == resourceName then
|
||||
if Config.Dependency.UseTarget == 'qb' then
|
||||
for k,v in ipairs(Config.WatchLoc) do
|
||||
exports['qb-target']:RemoveZone("spycam_watch"..k)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
102
resources/[jobs]/[police]/[spy-bodycam]/spy-bodycam/config.lua
Normal file
102
resources/[jobs]/[police]/[spy-bodycam]/spy-bodycam/config.lua
Normal file
|
@ -0,0 +1,102 @@
|
|||
-- ███████╗██████╗ ██╗ ██╗ ██████╗ ██████╗ ██████╗ ██╗ ██╗ ██████╗ █████╗ ███╗ ███╗
|
||||
-- ██╔════╝██╔══██╗╚██╗ ██╔╝ ██╔══██╗██╔═══██╗██╔══██╗╚██╗ ██╔╝██╔════╝██╔══██╗████╗ ████║
|
||||
-- ███████╗██████╔╝ ╚████╔╝ ██████╔╝██║ ██║██║ ██║ ╚████╔╝ ██║ ███████║██╔████╔██║
|
||||
-- ╚════██║██╔═══╝ ╚██╔╝ ██╔══██╗██║ ██║██║ ██║ ╚██╔╝ ██║ ██╔══██║██║╚██╔╝██║
|
||||
-- ███████║██║ ██║ ██████╔╝╚██████╔╝██████╔╝ ██║ ╚██████╗██║ ██║██║ ╚═╝ ██║
|
||||
-- ╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝
|
||||
|
||||
Config = Config or {}
|
||||
|
||||
Config.Framework = 'qb' -- qb | esx
|
||||
|
||||
if Config.Framework == 'qb' then -- Dont touch this part
|
||||
QBCore = exports["qb-core"]:GetCoreObject()
|
||||
elseif Config.Framework == 'esx' then
|
||||
ESX = exports.es_extended:getSharedObject()
|
||||
end
|
||||
|
||||
Config.Dependency = {
|
||||
UseTarget = 'qb', -- qb | ox
|
||||
UseInventory = 'qb', -- qb | ox | esx
|
||||
UseProgress = 'qb', -- qb | ox | esx
|
||||
UseMenu = 'qb', -- qb | ox | esx
|
||||
UseNotify = 'qb', -- qb | ox | esx
|
||||
UseAppearance = 'illenium', -- qb | illenium | false
|
||||
}
|
||||
|
||||
Config.ExitCamKey = 'BACK'
|
||||
|
||||
Config.CameraEffect = {
|
||||
bodycam = 'Island_CCTV_ChannelFuzz',
|
||||
dashcam = 'TinyRacerMoBlur',
|
||||
}
|
||||
|
||||
Config.ForceViewCam = false -- Forces cam view to first person when recording.
|
||||
Config.RecordTime = 30 --Recording Time | 30 = 30 seconds | [*note: Dont increase the time too much or it may not upload to any service you are using for example - discord.]
|
||||
|
||||
Config.PropLoc = { -- Change prop position according to ur clothing pack.
|
||||
male = {
|
||||
bone = 24818,
|
||||
pos = vector3(0.16683200771922, 0.11320925137666, 0.11986595654326),
|
||||
rot = vector3(-14.502323044318, 82.191095946679, -164.22066869048),
|
||||
},
|
||||
female = {
|
||||
bone = 24818,
|
||||
pos = vector3(0.16683200771922, 0.11320925137666, 0.11986595654326),
|
||||
rot = vector3(-14.502323044318, 82.191095946679, -164.22066869048),
|
||||
},
|
||||
}
|
||||
|
||||
Config.AllowedJobs = { -- Only these jobs can use bodycam/dashcam item.
|
||||
'police',
|
||||
'ambulance',
|
||||
}
|
||||
|
||||
Config.AllowedClass = {18} -- Vehicle classes allowed to use the dashcam feature.
|
||||
|
||||
Config.WatchLoc = {
|
||||
[1] = {
|
||||
coords = vector3(440.149445, -979.437378, 30.453491),
|
||||
rad = 1.5,
|
||||
debug = false,
|
||||
jobCam = {'police','ambulance'}, -- jobs mentioned here are shown in the list | false = able to view all the bodycams
|
||||
carCam = { -- false = able to view all the dashcams
|
||||
job = {'police'}, -- Jobs that activate dashcams shown in the list | false excludes.
|
||||
class = {18} -- Dashcam activated on these vehicleclass shown in the list | false excludes.
|
||||
},
|
||||
targetAuth = {'police'}, -- jobs mentioned here can use this location from target | false = everyone can access this location
|
||||
|
||||
}, -- You can add more locations
|
||||
}
|
||||
|
||||
Config.DebugCamera = false -- Make it true if you want to get new camera offset for some vehicle.
|
||||
Config.VehCamOffset = {
|
||||
[`police2`] = {0.000000, 0.330000, 0.530000},
|
||||
-- [`18chgr2`] = {0.000000, 0.510000, 0.630000}, -- Example vehicle. The script comes with its own offset finder just set DebugCamera to true and get the offset.
|
||||
-- [`vehiclespawncode`] = {0.000000, 0.510000, 0.630000},
|
||||
}
|
||||
|
||||
-- Vehicle Classes:
|
||||
-- 0: Compacts
|
||||
-- 1: Sedans
|
||||
-- 2: SUVs
|
||||
-- 3: Coupes
|
||||
-- 4: Muscle
|
||||
-- 5: Sports Classics
|
||||
-- 6: Sports
|
||||
-- 7: Super
|
||||
-- 8: Motorcycles
|
||||
-- 9: Off-road
|
||||
-- 10: Industrial
|
||||
-- 11: Utility
|
||||
-- 12: Vans
|
||||
-- 13: Cycles
|
||||
-- 14: Boats
|
||||
-- 15: Helicopters
|
||||
-- 16: Planes
|
||||
-- 17: Service
|
||||
-- 18: Emergency
|
||||
-- 19: Military
|
||||
-- 20: Commercial
|
||||
-- 21: Trains
|
||||
-- 22: Open Wheel
|
|
@ -0,0 +1,29 @@
|
|||
fx_version 'cerulean'
|
||||
|
||||
game "gta5"
|
||||
|
||||
author "Spy-Script"
|
||||
version '2.5.6'
|
||||
|
||||
lua54 'yes'
|
||||
|
||||
ui_page 'web/index.html'
|
||||
|
||||
shared_script {
|
||||
"config.lua",
|
||||
'@ox_lib/init.lua',
|
||||
}
|
||||
|
||||
server_script {
|
||||
"server/**",
|
||||
'@oxmysql/lib/MySQL.lua',
|
||||
}
|
||||
|
||||
client_script {
|
||||
'client/**',
|
||||
}
|
||||
|
||||
files {
|
||||
'web/**',
|
||||
"node_modules/fivem-game-view/**/*",
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 7.7 KiB |
Binary file not shown.
After Width: | Height: | Size: 7.2 KiB |
|
@ -0,0 +1,24 @@
|
|||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET NAMES utf8 */;
|
||||
/*!50503 SET NAMES utf8mb4 */;
|
||||
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
|
||||
/*!40103 SET TIME_ZONE='+00:00' */;
|
||||
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
||||
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
||||
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `spy_bodycam` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`job` varchar(255) NOT NULL,
|
||||
`videolink` longtext NOT NULL,
|
||||
`street` varchar(255) NOT NULL,
|
||||
`date` varchar(255) NOT NULL,
|
||||
`playername` varchar(255) NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=42 DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
|
||||
/*!40103 SET TIME_ZONE=IFNULL(@OLD_TIME_ZONE, 'system') */;
|
||||
/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;
|
||||
/*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */;
|
||||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||
/*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */;
|
|
@ -0,0 +1,373 @@
|
|||
PlayerOnBodycam = {}
|
||||
GlobalState.PlayerOnBodycam = PlayerOnBodycam
|
||||
CarsOnBodycam = {}
|
||||
GlobalState.CarsOnBodycam = CarsOnBodycam
|
||||
|
||||
lib.callback.register('spy-bodycam:servercb:getPedCoords', function(source, targetId)
|
||||
local targetPed = GetPlayerPed(targetId)
|
||||
if targetPed == 0 then return false end
|
||||
local targetCoords = GetEntityCoords(targetPed)
|
||||
if targetCoords then return targetCoords end
|
||||
|
||||
end)
|
||||
|
||||
lib.callback.register('spy-bodycam:servercb:getCarCoords', function(source, netId)
|
||||
local veh = NetworkGetEntityFromNetworkId(netId)
|
||||
if DoesEntityExist(veh) then
|
||||
local targetCoords = GetEntityCoords(veh)
|
||||
if targetCoords then return targetCoords end
|
||||
else
|
||||
CarsOnBodycam[netId] = nil
|
||||
GlobalState.CarsOnBodycam = CarsOnBodycam
|
||||
return false
|
||||
end
|
||||
end)
|
||||
|
||||
lib.callback.register('spy-bodycam:servercb:getNamESX', function(source)
|
||||
local xPlayer = ESX.GetPlayerFromId(source)
|
||||
local Name = xPlayer.getName()
|
||||
if Name then return Name end
|
||||
end)
|
||||
|
||||
RegisterNetEvent('spy-bodycam:server:toggleList',function(bool)
|
||||
local src = source
|
||||
if bool then
|
||||
if Config.Framework == 'qb' then
|
||||
local Player = QBCore.Functions.GetPlayer(src)
|
||||
local Name = Player.PlayerData.charinfo.firstname .. " " .. Player.PlayerData.charinfo.lastname
|
||||
local Job = Player.PlayerData.job.label
|
||||
local Rank = Player.PlayerData.job.grade.name
|
||||
PlayerOnBodycam[src] = {
|
||||
name = Name,
|
||||
jobkey = Player.PlayerData.job.name,
|
||||
job = Job,
|
||||
rank = Rank,
|
||||
}
|
||||
else
|
||||
local xPlayer = ESX.GetPlayerFromId(src)
|
||||
local Name = xPlayer.getName()
|
||||
local Job = xPlayer.getJob().label
|
||||
local Rank = xPlayer.getJob().grade_label
|
||||
PlayerOnBodycam[src] = {
|
||||
name = Name,
|
||||
jobkey = xPlayer.getJob().name,
|
||||
job = Job,
|
||||
rank = Rank,
|
||||
}
|
||||
end
|
||||
|
||||
GlobalState.PlayerOnBodycam = PlayerOnBodycam
|
||||
else
|
||||
PlayerOnBodycam[src] = nil
|
||||
GlobalState.PlayerOnBodycam = PlayerOnBodycam
|
||||
end
|
||||
SetTimeout(1000, function()
|
||||
TriggerClientEvent('spy-bodycam:updatelisteffect',-1)
|
||||
end)
|
||||
|
||||
end)
|
||||
|
||||
RegisterNetEvent('spy-bodycam:server:toggleListCars',function(bool,entityId,carPlate,carName,carClass)
|
||||
local src = source
|
||||
if bool then
|
||||
local veh = NetworkGetEntityFromNetworkId(entityId)
|
||||
if DoesEntityExist(veh) then
|
||||
local jobkey
|
||||
local Name
|
||||
if Config.Framework == 'qb' then
|
||||
local Player = QBCore.Functions.GetPlayer(src)
|
||||
jobkey = Player.PlayerData.job.name
|
||||
Name = Player.PlayerData.charinfo.firstname .. " " .. Player.PlayerData.charinfo.lastname
|
||||
else
|
||||
local xPlayer = ESX.GetPlayerFromId(src)
|
||||
jobkey = xPlayer.getJob().name
|
||||
Name = xPlayer.getName()
|
||||
end
|
||||
CarsOnBodycam[entityId] = {
|
||||
plate = carPlate,
|
||||
carname = carName,
|
||||
name = Name,
|
||||
jobkey = jobkey,
|
||||
carclass = carClass,
|
||||
}
|
||||
GlobalState.CarsOnBodycam = CarsOnBodycam
|
||||
else
|
||||
CarsOnBodycam[entityId] = nil
|
||||
GlobalState.CarsOnBodycam = CarsOnBodycam
|
||||
end
|
||||
else
|
||||
CarsOnBodycam[entityId] = nil
|
||||
GlobalState.CarsOnBodycam = CarsOnBodycam
|
||||
end
|
||||
SetTimeout(1000, function()
|
||||
TriggerClientEvent('spy-bodycam:updatelisteffectcar',-1)
|
||||
end)
|
||||
end)
|
||||
|
||||
RegisterNetEvent('spy-bodycam:server:ReqDeleteDecoyPed',function()
|
||||
if not Config.Dependency.UseAppearance then return end
|
||||
local src = source
|
||||
TriggerClientEvent('spy-bodycam:client:deleteDecoyPed',-1,src)
|
||||
end)
|
||||
|
||||
RegisterNetEvent('spy-bodycam:server:ReqDecoyPed', function(cid, pedCoords)
|
||||
if not Config.Dependency.UseAppearance then return end
|
||||
local src = source
|
||||
local result
|
||||
local function handleDecoyPed(model, skin, pedCoords, src)
|
||||
local nPlayers = lib.getNearbyPlayers(vector3(pedCoords.x, pedCoords.y, pedCoords.z), 150)
|
||||
if nPlayers then
|
||||
for i = 1, #nPlayers do
|
||||
TriggerClientEvent('spy-bodycam:client:createDecoyPed', nPlayers[i].id, model, skin, pedCoords, src)
|
||||
end
|
||||
end
|
||||
end
|
||||
if Config.Framework == 'qb' then
|
||||
result = MySQL.query.await('SELECT * FROM playerskins WHERE citizenid = ? AND active = ?', {cid, 1})
|
||||
if result[1] ~= nil then
|
||||
local skinData = json.decode(result[1].skin)
|
||||
if Config.Dependency.UseAppearance == 'qb' then
|
||||
handleDecoyPed(tonumber(result[1].model), skinData, pedCoords, src)
|
||||
elseif Config.Dependency.UseAppearance == 'illenium' then
|
||||
handleDecoyPed(joaat(skinData.model), skinData, pedCoords, src)
|
||||
end
|
||||
end
|
||||
elseif Config.Framework == 'esx' then
|
||||
result = MySQL.single.await("SELECT skin FROM users WHERE identifier = ?", {cid})
|
||||
if result then
|
||||
local skinData = json.decode(result.skin)
|
||||
if Config.Dependency.UseAppearance == 'illenium' then
|
||||
handleDecoyPed(joaat(skinData.model), skinData, pedCoords, src)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
Citizen.CreateThread(function()
|
||||
if Config.Dependency.UseInventory == 'qb' then
|
||||
QBCore.Functions.CreateUseableItem('bodycam', function(source, item)
|
||||
TriggerClientEvent('spy-bodycam:bodycamstatus',source)
|
||||
end)
|
||||
QBCore.Functions.CreateUseableItem('dashcam', function(source, item)
|
||||
TriggerClientEvent('spy-bodycam:toggleCarCam',source)
|
||||
end)
|
||||
elseif Config.Dependency.UseInventory == 'esx' then
|
||||
ESX.RegisterUsableItem('bodycam', function(playerId)
|
||||
TriggerClientEvent('spy-bodycam:bodycamstatus', playerId)
|
||||
end)
|
||||
ESX.RegisterUsableItem('dashcam', function(playerId)
|
||||
TriggerClientEvent('spy-bodycam:toggleCarCam', playerId)
|
||||
end)
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent('spy-bodycam:server:logVideoDetails', function(videoUrl,streetName)
|
||||
local src = source
|
||||
local offName
|
||||
local offJob
|
||||
local offRank
|
||||
local jobKey
|
||||
local date = os.date('%Y-%m-%d')
|
||||
|
||||
if Config.Framework == 'qb' then
|
||||
local Player = QBCore.Functions.GetPlayer(src)
|
||||
offName = Player.PlayerData.charinfo.firstname .. " " .. Player.PlayerData.charinfo.lastname
|
||||
offJob = Player.PlayerData.job.label
|
||||
offRank = Player.PlayerData.job.grade.name
|
||||
jobKey = Player.PlayerData.job.name
|
||||
else
|
||||
local xPlayer = ESX.GetPlayerFromId(src)
|
||||
offName = xPlayer.getName()
|
||||
offJob = xPlayer.getJob().label
|
||||
offRank = xPlayer.getJob().grade_label
|
||||
jobKey = xPlayer.getJob().name
|
||||
end
|
||||
|
||||
---SQL UPLOAD
|
||||
MySQL.Async.execute('INSERT INTO spy_bodycam (job, videolink, street, date, playername) VALUES (@job, @videolink, @street, @date, @playername)', {
|
||||
['@job'] = jobKey,
|
||||
['@videolink'] = videoUrl,
|
||||
['@street'] = streetName,
|
||||
['@date'] = date,
|
||||
['@playername'] = offName
|
||||
}, function(rowsChanged) end)
|
||||
|
||||
if Upload.DiscordLogs.Enabled then
|
||||
local defwebhook
|
||||
local author
|
||||
if Upload.JobUploads[jobKey] then
|
||||
defwebhook = Upload.JobUploads[jobKey].webhook
|
||||
author = Upload.JobUploads[jobKey].author
|
||||
else
|
||||
defwebhook = Upload.DefaultUploads.webhook
|
||||
author = Upload.DefaultUploads.author
|
||||
end
|
||||
local embedData = {
|
||||
{
|
||||
title = Upload.DiscordLogs.Title,
|
||||
color = 16761035,
|
||||
fields = {
|
||||
{ name = "Name:", value = offName, inline = false },
|
||||
{ name = "Job:", value = offJob, inline = false },
|
||||
{ name = "Job Rank:", value = offRank, inline = false },
|
||||
},
|
||||
footer = {
|
||||
text = "Date: " .. os.date("!%Y-%m-%d %H:%M:%S", os.time()),
|
||||
icon_url = "https://i.imgur.com/CuSyeZT.png",
|
||||
},
|
||||
author = author
|
||||
}
|
||||
}
|
||||
PerformHttpRequest(defwebhook, function() end, 'POST', json.encode({ username = Upload.DiscordLogs.Username, embeds = embedData}), { ['Content-Type'] = 'application/json' })
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent('spy-bodycam:server:deleteVideoDB', function(videoUrl)
|
||||
local src = source
|
||||
if not videoUrl or videoUrl == '' then return end
|
||||
MySQL.Async.execute('DELETE FROM spy_bodycam WHERE videolink = @videolink', {
|
||||
['@videolink'] = videoUrl
|
||||
}, function(rowsChanged)
|
||||
if rowsChanged > 0 then
|
||||
local jobKey
|
||||
local isBoss
|
||||
if Config.Framework == 'qb' then
|
||||
local Player = QBCore.Functions.GetPlayer(src)
|
||||
jobKey = Player.PlayerData.job.name
|
||||
isBoss = Player.PlayerData.job.isboss
|
||||
else
|
||||
local xPlayer = ESX.GetPlayerFromId(src)
|
||||
jobKey = xPlayer.getJob().name
|
||||
isBoss = (xPlayer.getJob().grade_name == 'boss')
|
||||
end
|
||||
MySQL.Async.fetchAll('SELECT * FROM spy_bodycam WHERE job = @job ORDER BY id DESC', {
|
||||
['@job'] = jobKey
|
||||
}, function(records)
|
||||
TriggerClientEvent('spy-bodycam:client:refreshRecords', src, records, isBoss)
|
||||
end)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
RegisterNetEvent('spy-bodycam:server:showrecordingUI', function()
|
||||
local src = source
|
||||
local offJob
|
||||
local jobKey
|
||||
local isBoss
|
||||
if Config.Framework == 'qb' then
|
||||
local Player = QBCore.Functions.GetPlayer(src)
|
||||
offJob = Player.PlayerData.job.label
|
||||
jobKey = Player.PlayerData.job.name
|
||||
isBoss = Player.PlayerData.job.isboss
|
||||
else
|
||||
local xPlayer = ESX.GetPlayerFromId(src)
|
||||
offJob = xPlayer.getJob().label
|
||||
jobKey = xPlayer.getJob().name
|
||||
if xPlayer.getJob().grade_name == 'boss' then isBoss = true else isBoss = false end
|
||||
end
|
||||
MySQL.Async.fetchAll('SELECT * FROM spy_bodycam WHERE job = @job ORDER BY id DESC', {
|
||||
['@job'] = jobKey
|
||||
}, function(records)
|
||||
TriggerClientEvent('spy-bodycam:client:openRecords', src, records, offJob, isBoss)
|
||||
end)
|
||||
end)
|
||||
|
||||
lib.addCommand('recordcam', {
|
||||
help = 'Record bodycam footage.',
|
||||
restricted = false
|
||||
}, function(source, args, raw)
|
||||
local src = source
|
||||
if PlayerOnBodycam[src] then
|
||||
local defwebhook
|
||||
if Upload.ServiceUsed == 'discord' then
|
||||
local jobKey
|
||||
if Config.Framework == 'qb' then
|
||||
local Player = QBCore.Functions.GetPlayer(src)
|
||||
jobKey = Player.PlayerData.job.name
|
||||
else
|
||||
local xPlayer = ESX.GetPlayerFromId(src)
|
||||
jobKey = xPlayer.getJob().name
|
||||
end
|
||||
if Upload.JobUploads[jobKey] then
|
||||
defwebhook = Upload.JobUploads[jobKey].webhook
|
||||
else
|
||||
defwebhook = Upload.DefaultUploads.webhook
|
||||
end
|
||||
elseif Upload.ServiceUsed == 'fivemanage' or Upload.ServiceUsed == 'fivemerr' then
|
||||
defwebhook = Upload.Token
|
||||
end
|
||||
TriggerClientEvent('spy-bodycam:client:startRec',src,defwebhook,Upload.ServiceUsed)
|
||||
else
|
||||
NotifyPlayerSv('Bodycam not turned on!','error',3000,src)
|
||||
end
|
||||
end)
|
||||
|
||||
function NotifyPlayerSv(msg,type,time,src)
|
||||
if Config.Dependency.UseNotify == 'ox' then
|
||||
TriggerClientEvent('ox_lib:notify', src, { type = type or "success", title = '', description = msg, duration = time })
|
||||
elseif Config.Dependency.UseNotify == 'qb' then
|
||||
TriggerClientEvent("QBCore:Notify", src, msg, type,time)
|
||||
elseif Config.Dependency.UseNotify == 'esx' then
|
||||
TriggerClientEvent("esx:showNotification", src, msg, type)
|
||||
end
|
||||
end
|
||||
|
||||
-- You can remove this if u want more optimization from the script.
|
||||
-- If the vehicle is deleted or send to garage it removes it from the list.
|
||||
Citizen.CreateThread(function()
|
||||
while true do
|
||||
for entityId, _ in pairs(CarsOnBodycam) do
|
||||
local veh = NetworkGetEntityFromNetworkId(entityId)
|
||||
if not DoesEntityExist(veh) then
|
||||
CarsOnBodycam[entityId] = nil
|
||||
GlobalState.CarsOnBodycam = CarsOnBodycam
|
||||
end
|
||||
end
|
||||
Citizen.Wait(60000) -- Wait for 1 min before checking again
|
||||
end
|
||||
end)
|
||||
|
||||
-- Script Version Checker
|
||||
local localVersion = GetResourceMetadata(GetCurrentResourceName(), 'version')
|
||||
local fxManifestUrl = "https://raw.githubusercontent.com/Your-Spy/spy-bodycam/main/%5Bspy-bodycam%5D/spy-bodycam/fxmanifest.lua"
|
||||
|
||||
local function extractVersion(fxManifestContent)
|
||||
for line in string.gmatch(fxManifestContent, "[^\r\n]+") do
|
||||
if line:find("^version%s+'(.-)'$") then
|
||||
return line:match("^version%s+'(.-)'$")
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function checkForUpdates()
|
||||
PerformHttpRequest(fxManifestUrl, function(statusCode, response, headers)
|
||||
print([[^4
|
||||
╔───────────────────────────────────────────────────────────────────────╗
|
||||
____ ______ __ ____ ___ ______ ______ _ __ __
|
||||
/ ___|| _ \ \ / / | __ ) / _ \| _ \ \ / / ___| / \ | \/ |
|
||||
\___ \| |_) \ V / _____ | _ \| | | | | | \ V / | / _ \ | |\/| |
|
||||
___) | __/ | | |_____| | |_) | |_| | |_| || || |___ / ___ \| | | |
|
||||
|____/|_| |_| |____/ \___/|____/ |_| \____/_/ \_\_| |_|
|
||||
|
||||
╚───────────────────────────────────────────────────────────────────────╝
|
||||
]])
|
||||
if statusCode == 200 then
|
||||
local remoteVersion = extractVersion(response)
|
||||
if remoteVersion and remoteVersion ~= localVersion then
|
||||
print("^2NEW UPDATE: ^2" .. remoteVersion .. "^3 | ^1CURRENT: " .. localVersion.." ^9>> Download new version from github")
|
||||
else
|
||||
print("^2You are on latest version: ^2" .. localVersion)
|
||||
end
|
||||
else
|
||||
print("^1Failed to check for updates. Status code: " .. statusCode)
|
||||
end
|
||||
end, "GET", "", {["Content-Type"] = "text/plain"})
|
||||
end
|
||||
|
||||
AddEventHandler('onResourceStart', function(resourceName)
|
||||
if GetCurrentResourceName() == resourceName then
|
||||
checkForUpdates()
|
||||
end
|
||||
end)
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
Upload = Upload or {}
|
||||
|
||||
-- U have to start the recording using /recordcam and the recording will be uploaded to any of the service below.
|
||||
|
||||
Upload.ServiceUsed = 'fivemanage' -- discord | fivemanage | fivemerr
|
||||
Upload.Token = 'c6sTqXXzOJksi7hKs0vMFuN8yUmJfcb1' -- fivemanage or fivemerr | [*note - for discord webhook is to be changed below not here]
|
||||
|
||||
-- FOR DISCORD LOGS
|
||||
Upload.DiscordLogs = {
|
||||
Enabled = false,
|
||||
Username = 'Spy Bodycam Records', -- Bot Username
|
||||
Title = 'Bodycam Records', -- Message Title
|
||||
}
|
||||
|
||||
-- Upload Hooks if Upload.ServiceUsed = discord
|
||||
Upload.DefaultUploads = { -- Default Upload of log if job not mentioned in Upload.JobUploads.
|
||||
webhook = 'YOUR_WEBHOOK',
|
||||
author = {
|
||||
name = "Spy Bodycam",
|
||||
icon_url = "https://i.imgur.com/tMyAdkz.png"
|
||||
}
|
||||
}
|
||||
|
||||
Upload.JobUploads = { -- Job Speific Uploads
|
||||
['police'] = {
|
||||
webhook = 'YOUR_WEBHOOK',
|
||||
author = {
|
||||
name = "Police Department",
|
||||
icon_url = "https://i.imgur.com/tMyAdkz.png"
|
||||
}
|
||||
}, -- Add more here
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 84 KiB |
|
@ -0,0 +1,87 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Spy Bodycam</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.1/css/all.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<canvas style="display: none;" width="1280px" height="720px"></canvas>
|
||||
<div class="overlayCont">
|
||||
<div class="overlayBody">
|
||||
<div class="bodyInfobox">
|
||||
<div class="bodyDate"><i class="fa-solid fa-circle fa-fade bodyIcon"></i></i>08-04 23:58:45-0500</div>
|
||||
<div class="bodyNum">BODY 22 X6070511N</div>
|
||||
<div class="bodyCallsign">(222) Spy Dev</div>
|
||||
</div>
|
||||
<img src="images/brand_logo.png" alt="">
|
||||
<div class="AddonIcons"><i class="fa-solid fa-battery-three-quarters"></i><i class="fa-solid fa-wifi"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="RecordInfo">
|
||||
<div class="recordBox">
|
||||
<div class="HeadText"><i class="fa-solid fa-video fa-fade bodyIcon"></i>Recording Started</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="currWatchCont">
|
||||
<div class="watchInfo">
|
||||
<div class="userLaber">
|
||||
<i class="fa-solid fa-video watchIcon" style="color: #cf0707;"></i>
|
||||
<span class="watchInText typeCam">Bodycam</span>
|
||||
<br>
|
||||
<span class="watchInText plateText">Plate : 726G21</span>
|
||||
<br>
|
||||
<span class="watchInText carText">Car : Crown Vic</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="backInfo">
|
||||
<div class="backLabel"><i class="fa-solid fa-video-slash backIcon"></i><span class="backInText">E</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="recCont">
|
||||
<div class="recMain">
|
||||
<div class="recTitle">
|
||||
<div class="recTitleTxt">Bodycam Recordings</div>
|
||||
<div class="recDesc">POLICE DATABASE</div>
|
||||
<img src="images/brand_logo.png" alt="" class="recLogo">
|
||||
</div>
|
||||
<div class="RecBox">
|
||||
<div class="recInnerScroll"></div>
|
||||
</div>
|
||||
<div class="recFilters">
|
||||
<div class="recSearch">
|
||||
<div class="searchText"><i class="fas fa-search"></i></div>
|
||||
<input type="text" class="searchInput" placeholder="Seach Footage...">
|
||||
</div>
|
||||
<div class="recDateF">
|
||||
<input type="date" class="selectedDate" placeholder="Select Date">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="vidplaycont">
|
||||
<div class="vidMain">
|
||||
<video class="vidPlayer" controls autoplay loop>
|
||||
<source src="https://cdn.discordapp.com/attachments/1257923291512045568/1263565598277763082/video.webm?ex=669ab2aa&is=6699612a&hm=a6afae0dd5179e186a56fecdb506a85f3769ad9db5b33ec14efede37454a1ba7&" type="video/webm">
|
||||
</video>
|
||||
<div class="vidPlayerIcon"><i class="fa-solid fa-circle-xmark"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="askMain">
|
||||
<div class="askIn">
|
||||
<div class="askIcon"><i class="fa-solid fa-trash fa-bounce"></i></div>
|
||||
<h1 class="askTitle">Are you sure?</h1>
|
||||
<div class="askBtnCont">
|
||||
<div class="askBtn">Yes</div>
|
||||
<div class="askBtn">No</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<audio id="beep-sound" src="audio/on_sound.mp3"></audio>
|
||||
<audio id="off-sound" src="audio/off_sound.mp3"></audio>
|
||||
<script src="js/jquery-3.7.1.min.js"></script>
|
||||
<script type="module" src="js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,394 @@
|
|||
import { GameView } from './gameview.js';
|
||||
const gameview = new GameView();
|
||||
let isRecording = false;
|
||||
let recordingTimeout;
|
||||
|
||||
$(document).ready(function () {
|
||||
$('.overlayCont').hide();
|
||||
$('.currWatchCont').hide();
|
||||
$('.RecordInfo').hide();
|
||||
$('.recCont').hide();
|
||||
$('.askMain').hide();
|
||||
$('.vidplaycont').hide();
|
||||
const beepSound = document.getElementById('beep-sound');
|
||||
const offSound = document.getElementById('off-sound');
|
||||
|
||||
function updateTime() {
|
||||
const date = new Date();
|
||||
date.setUTCHours(date.getUTCHours() - 5);
|
||||
|
||||
const month = ("0" + (date.getMonth() + 1)).slice(-2);
|
||||
const day = ("0" + date.getDate()).slice(-2);
|
||||
const hours = ("0" + date.getHours()).slice(-2);
|
||||
const minutes = ("0" + date.getMinutes()).slice(-2);
|
||||
const seconds = ("0" + date.getSeconds()).slice(-2);
|
||||
|
||||
const gameTime = `${month}-${day} ${hours}:${minutes}:${seconds}-0500`;
|
||||
|
||||
$('.bodyDate').html('<i class="fa-solid fa-circle fa-fade bodyIcon"></i>' + gameTime);
|
||||
}
|
||||
|
||||
let interval;
|
||||
|
||||
window.addEventListener('message', function (event) {
|
||||
const data = event.data;
|
||||
if (data.action === 'open') {
|
||||
updateTime();
|
||||
interval = setInterval(updateTime, 1000);
|
||||
$('.bodyNum').text(data.bodyname);
|
||||
$('.bodyCallsign').text(data.callsign);
|
||||
$('.overlayCont').removeClass('popOut').addClass('popIn').show();
|
||||
beepSound.play();
|
||||
} else if (data.action === 'close') {
|
||||
clearInterval(interval);
|
||||
$('.overlayCont').removeClass('popIn').addClass('popOut').one('animationend', function () {
|
||||
$(this).hide();
|
||||
$(this).removeClass('popOut');
|
||||
});
|
||||
offSound.play();
|
||||
}
|
||||
if (data.action === 'openWatch') {
|
||||
let typeCam;
|
||||
let plateText;
|
||||
let carText;
|
||||
|
||||
if (data.debug) {
|
||||
var controlsHtml = `
|
||||
<span class="watchInText typeCam">Controls</span>
|
||||
<br>
|
||||
<span class="watchInText typeCam">W -> Up</span>
|
||||
<br>
|
||||
<span class="watchInText typeCam">S -> Down</span>
|
||||
<br>
|
||||
<span class="watchInText typeCam">A -> Left</span>
|
||||
<br>
|
||||
<span class="watchInText typeCam">D -> Right</span>
|
||||
<br>
|
||||
<span class="watchInText typeCam">Q -> Forward</span>
|
||||
<br>
|
||||
<span class="watchInText typeCam">E -> Backward</span>
|
||||
`;
|
||||
document.querySelector('.userLaber').innerHTML = controlsHtml;
|
||||
} else {
|
||||
if (data.isbodycam) {
|
||||
typeCam = "BODYCAM";
|
||||
plateText = "CamID: " + data.bodyId;
|
||||
carText = "Name: " + data.name;
|
||||
} else {
|
||||
typeCam = "DASHCAM";
|
||||
plateText = "Plate: " + data.bodyId;
|
||||
carText = "Car: " + data.name;
|
||||
}
|
||||
document.querySelector('.typeCam').textContent = typeCam;
|
||||
document.querySelector('.plateText').textContent = plateText;
|
||||
document.querySelector('.carText').textContent = carText;
|
||||
}
|
||||
$('.backInText').text(data.exitKey);
|
||||
$('.currWatchCont').fadeIn();
|
||||
} else if (data.action === 'closeWatch') {
|
||||
$('.currWatchCont').fadeOut();
|
||||
}
|
||||
if (data.action === 'toggle_record') {
|
||||
clearTimeout(recordingTimeout);
|
||||
if (!isRecording) {
|
||||
// Start recording
|
||||
isRecording = true;
|
||||
$('.HeadText').html('<i class="fa-solid fa-circle fa-fade bodyIcon" style="color: rgb(173, 8, 8);"></i>' + 'Recording Started');
|
||||
$('.RecordInfo').fadeIn();
|
||||
startRecording(data.hook, data.service);
|
||||
recordingTimeout = setTimeout(() => {
|
||||
if (isRecording) {
|
||||
// Stop recording automatically after 30 seconds
|
||||
isRecording = false;
|
||||
$('.HeadText').html('<i class="fa-solid fa-circle bodyIcon" style="color: white;"></i>' + 'Recording Stopped');
|
||||
setTimeout(() => {
|
||||
$('.RecordInfo').fadeOut();
|
||||
}, 2000);
|
||||
stopRecording();
|
||||
}
|
||||
}, data.recTiming * 1000);
|
||||
} else {
|
||||
// Stop recording
|
||||
isRecording = false;
|
||||
$('.HeadText').html('<i class="fa-solid fa-circle bodyIcon" style="color: white;"></i>' + 'Recording Stopped');
|
||||
clearTimeout(recordingTimeout);
|
||||
setTimeout(() => {
|
||||
$('.RecordInfo').fadeOut();
|
||||
}, 2000);
|
||||
stopRecording();
|
||||
}
|
||||
}
|
||||
if (data.action === 'cancel_rec_force') {
|
||||
if (isRecording) {
|
||||
// Force stop recording
|
||||
isRecording = false;
|
||||
$('.HeadText').html('<i class="fa-solid fa-circle bodyIcon" style="color: white;"></i>' + 'Recording Stopped');
|
||||
clearTimeout(recordingTimeout);
|
||||
setTimeout(() => {
|
||||
$('.RecordInfo').fadeOut();
|
||||
}, 1000);
|
||||
stopRecording();
|
||||
}
|
||||
}
|
||||
|
||||
// RECORDS SHOWING
|
||||
if (data.action === 'show_records') {
|
||||
$('.recInnerScroll').empty();
|
||||
$('.recDesc').text(`${data.jobTitle} Database`);
|
||||
|
||||
if (Array.isArray(data.recordData) && data.recordData.length > 0) {
|
||||
$.each(data.recordData, function (index, record) {
|
||||
var camBox = $(
|
||||
`
|
||||
<div class="camBox">
|
||||
<div class="camInfo">
|
||||
<div class="camTitle">${record.playername}<span class="camStreet"> [${record.street}]</span></div>
|
||||
<div class="camDesc">Date: ${record.date}</div>
|
||||
</div>
|
||||
<div class="camIcons">
|
||||
<div class="camShow" data-stored="${record.videolink}"><i class="fa-solid fa-eye"></i></div>
|
||||
${data.isBoss ? `<div class="camDelete"><i class="fa-solid fa-trash"></i></div>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
);
|
||||
$('.recInnerScroll').append(camBox);
|
||||
});
|
||||
$('.recCont').show();
|
||||
} else {
|
||||
$('.recInnerScroll').html('<h1 class="noRecAv">No records available</h1>');
|
||||
$('.recCont').show();
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh Records
|
||||
if (data.action === 'refreshrec') {
|
||||
$('.recInnerScroll').empty();
|
||||
if (Array.isArray(data.recordData) && data.recordData.length > 0) {
|
||||
$.each(data.recordData, function (index, record) {
|
||||
var camBox = $(
|
||||
`
|
||||
<div class="camBox">
|
||||
<div class="camInfo">
|
||||
<div class="camTitle">${record.playername}<span class="camStreet"> [${record.street}]</span></div>
|
||||
<div class="camDesc">Date: ${record.date}</div>
|
||||
</div>
|
||||
<div class="camIcons">
|
||||
<div class="camShow" data-stored="${record.videolink}"><i class="fa-solid fa-eye"></i></div>
|
||||
${data.isBoss ? `<div class="camDelete"><i class="fa-solid fa-trash"></i></div>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
);
|
||||
$('.recInnerScroll').append(camBox);
|
||||
});
|
||||
$('.recCont').show();
|
||||
} else {
|
||||
$('.recInnerScroll').html('<h1 class="noRecAv">No records available</h1>');
|
||||
$('.recCont').show();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let deleteUrl = '';
|
||||
|
||||
// SEARCH AND DATE FILTERS :)
|
||||
function updateVisibility(searchText) {
|
||||
$('.camBox').each(function () {
|
||||
var camTitleText = $(this).find('.camTitle').text().toLowerCase();
|
||||
var camDescDate = $(this).find('.camDesc').text().trim().replace('Date: ', '');
|
||||
if ((searchText === '' || camTitleText.includes(searchText)) &&
|
||||
($(this).css('display') !== 'none' || camDescDate === $('.selectedDate').val())
|
||||
) {
|
||||
$(this).show();
|
||||
}
|
||||
else if ((searchText === '' || camTitleText.includes(searchText)) &&
|
||||
($('.selectedDate').val() === '')
|
||||
) {
|
||||
$(this).show();
|
||||
} else {
|
||||
$(this).hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$('.searchInput').on('input', function () {
|
||||
var searchText = $(this).val().toLowerCase();
|
||||
updateVisibility(searchText);
|
||||
});
|
||||
|
||||
$('.selectedDate').on('change', function () {
|
||||
var selectedDate = $(this).val();
|
||||
$('.camBox').each(function () {
|
||||
var camDescDate = $(this).find('.camDesc').text().trim().replace('Date: ', '');
|
||||
if (selectedDate === '') {
|
||||
$(this).show();
|
||||
} else if (selectedDate === camDescDate) {
|
||||
$(this).show();
|
||||
} else {
|
||||
$(this).hide();
|
||||
}
|
||||
});
|
||||
var searchText = $('.searchInput').val().toLowerCase();
|
||||
updateVisibility(searchText);
|
||||
});
|
||||
|
||||
$(document).on('click', '.camDelete', function () {
|
||||
const camBox = $(this).closest('.camBox');
|
||||
deleteUrl = camBox.find('.camShow').data('stored');
|
||||
$('.askMain').fadeIn();
|
||||
});
|
||||
|
||||
$(document).on('click', '.askBtn:nth-child(1)', function () {
|
||||
$('.askMain').fadeOut();
|
||||
if (deleteUrl) {
|
||||
$.post(`https://${GetParentResourceName()}/deleteVideo`, JSON.stringify({
|
||||
vidurl: deleteUrl
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on('click', '.askBtn:nth-child(2)', function () {
|
||||
$('.askMain').fadeOut();
|
||||
});
|
||||
|
||||
$(document).on('click', '.camShow', function () {
|
||||
var videoUrl = $(this).data('stored');
|
||||
$('.vidPlayer').attr('src', videoUrl);
|
||||
$('.vidplaycont').show();
|
||||
});
|
||||
|
||||
$(document).on('click', '.vidPlayerIcon', function () {
|
||||
$('.vidplaycont').hide();
|
||||
$('.vidPlayer').attr('src', '');
|
||||
});
|
||||
|
||||
$(document).on('click', function (event) {
|
||||
if ($(event.target).is('.vidplaycont')) {
|
||||
$('.vidplaycont').hide();
|
||||
$('.vidPlayer').attr('src', '');
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on('keydown', function (e) {
|
||||
if (e.which === 27) {
|
||||
$('.askMain').hide();
|
||||
$('.searchInput').val('');
|
||||
$('.selectedDate').val('');
|
||||
$('.recCont').hide();
|
||||
$('.vidplaycont').hide();
|
||||
$('.vidPlayer').attr('src', '');
|
||||
$.post(`https://${GetParentResourceName()}/closeRecUI`, '{}');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
let mediaRecorder;
|
||||
let audioStream;
|
||||
const canvasElement = document.querySelector('canvas');
|
||||
|
||||
async function uploadBlob(videoBlob, hook, service) {
|
||||
$.post(`https://${GetParentResourceName()}/exitBodyCam`, '{}');
|
||||
const formData = new FormData();
|
||||
try {
|
||||
let response, responseData;
|
||||
if (service === 'fivemanage') {
|
||||
formData.append('video', videoBlob);
|
||||
response = await fetch('https://api.fivemanage.com/api/video', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: hook,
|
||||
},
|
||||
body: formData,
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to upload video to FiveManage: ${response.status}`);
|
||||
}
|
||||
responseData = await response.json();
|
||||
$.post(`https://${GetParentResourceName()}/videoLog`, JSON.stringify({
|
||||
vidurl: responseData.url
|
||||
}));
|
||||
} else if (service === 'fivemerr') {
|
||||
formData.append('file', videoBlob, 'video.webm');
|
||||
response = await fetch('https://api.fivemerr.com/v1/media/videos', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: hook,
|
||||
},
|
||||
body: formData,
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to upload video to Fivemerr: ${response.status}`);
|
||||
}
|
||||
responseData = await response.json();
|
||||
$.post(`https://${GetParentResourceName()}/videoLog`, JSON.stringify({
|
||||
vidurl: responseData.url
|
||||
}));
|
||||
} else if (service === 'discord') {
|
||||
formData.append('file', videoBlob, 'video.webm');
|
||||
response = await fetch(hook, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to upload video to Discord: ${response.status}`);
|
||||
}
|
||||
responseData = await response.json();
|
||||
$.post(`https://${GetParentResourceName()}/videoLog`, JSON.stringify({
|
||||
vidurl: responseData.attachments[0].url
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('^1[ERROR]:^3', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function startMicrophoneCapture() {
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||
return stream;
|
||||
} catch (error) {
|
||||
console.error('Microphone capture failed. Recording video only.');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function startRecording(hook, service) {
|
||||
audioStream = await startMicrophoneCapture();
|
||||
if (!isRecording){
|
||||
if (audioStream) {
|
||||
audioStream.getAudioTracks().forEach(track => track.stop());
|
||||
}
|
||||
return
|
||||
}
|
||||
console.log('Video Recording Started');
|
||||
const gameView = gameview.createGameView(canvasElement);
|
||||
const canvasStream = canvasElement.captureStream(30);
|
||||
|
||||
// Combine audio and video streams
|
||||
const combinedStream = new MediaStream([
|
||||
...canvasStream.getVideoTracks(),
|
||||
...(audioStream ? audioStream.getAudioTracks() : [])
|
||||
]);
|
||||
|
||||
const videoChunks = [];
|
||||
window.gameView = gameView;
|
||||
mediaRecorder = new MediaRecorder(combinedStream, { mimeType: 'video/webm;codecs=vp9' });
|
||||
mediaRecorder.start();
|
||||
mediaRecorder.ondataavailable = (e) => e.data.size > 0 && videoChunks.push(e.data);
|
||||
mediaRecorder.onstop = async () => {
|
||||
const videoBlob = new Blob(videoChunks, { type: 'video/webm' });
|
||||
if (videoBlob.size > 0) {
|
||||
uploadBlob(videoBlob, hook, service);
|
||||
}
|
||||
if (audioStream) {
|
||||
audioStream.getAudioTracks().forEach(track => track.stop());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function stopRecording() {
|
||||
if (mediaRecorder && mediaRecorder.state === 'recording') {
|
||||
mediaRecorder.stop();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,175 @@
|
|||
export class GameView {
|
||||
constructor() {
|
||||
this.vertexShaderSrc = `
|
||||
attribute vec2 a_position;
|
||||
attribute vec2 a_texcoord;
|
||||
uniform mat3 u_matrix;
|
||||
varying vec2 textureCoordinate;
|
||||
void main() {
|
||||
gl_Position = vec4(a_position, 0.0, 1.0);
|
||||
textureCoordinate = a_texcoord;
|
||||
}
|
||||
`;
|
||||
|
||||
this.fragmentShaderSrc = `
|
||||
varying highp vec2 textureCoordinate;
|
||||
uniform sampler2D external_texture;
|
||||
void main()
|
||||
{
|
||||
gl_FragColor = texture2D(external_texture, textureCoordinate);
|
||||
}
|
||||
`;
|
||||
|
||||
this.interval = null;
|
||||
}
|
||||
|
||||
makeShader = (gl, type, src) => {
|
||||
const shader = gl.createShader(type);
|
||||
gl.shaderSource(shader, src);
|
||||
gl.compileShader(shader);
|
||||
return shader;
|
||||
}
|
||||
|
||||
createTexture(gl) {
|
||||
const tex = gl.createTexture();
|
||||
|
||||
const texPixels = new Uint8Array([0, 0, 255, 255]);
|
||||
|
||||
gl.bindTexture(gl.TEXTURE_2D, tex);
|
||||
gl.texImage2D(
|
||||
gl.TEXTURE_2D,
|
||||
0,
|
||||
gl.RGBA,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
gl.RGBA,
|
||||
gl.UNSIGNED_BYTE,
|
||||
texPixels,
|
||||
);
|
||||
|
||||
gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
||||
gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
||||
gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||
|
||||
// Magic hook sequence
|
||||
gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.MIRRORED_REPEAT);
|
||||
gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
|
||||
|
||||
// Reset
|
||||
gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||
|
||||
return tex;
|
||||
}
|
||||
|
||||
createBuffers = (gl) => {
|
||||
const vertexBuff = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuff);
|
||||
gl.bufferData(
|
||||
gl.ARRAY_BUFFER,
|
||||
new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]),
|
||||
gl.STATIC_DRAW,
|
||||
);
|
||||
|
||||
const texBuff = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, texBuff);
|
||||
gl.bufferData(
|
||||
gl.ARRAY_BUFFER,
|
||||
new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]),
|
||||
gl.STATIC_DRAW,
|
||||
);
|
||||
|
||||
return { vertexBuff, texBuff };
|
||||
}
|
||||
|
||||
createProgram = (gl) => {
|
||||
const vertexShader = this.makeShader(gl, gl.VERTEX_SHADER, this.vertexShaderSrc);
|
||||
const fragmentShader = this.makeShader(gl, gl.FRAGMENT_SHADER, this.fragmentShaderSrc);
|
||||
|
||||
const program = gl.createProgram();
|
||||
|
||||
gl.attachShader(program, vertexShader);
|
||||
gl.attachShader(program, fragmentShader);
|
||||
gl.linkProgram(program);
|
||||
gl.useProgram(program);
|
||||
|
||||
const vloc = gl.getAttribLocation(program, 'a_position');
|
||||
const tloc = gl.getAttribLocation(program, 'a_texcoord');
|
||||
|
||||
return { program, vloc, tloc };
|
||||
}
|
||||
|
||||
createStuff(gl) {
|
||||
const tex = this.createTexture(gl);
|
||||
const { program, vloc, tloc } = this.createProgram(gl);
|
||||
const { vertexBuff, texBuff } = this.createBuffers(gl);
|
||||
|
||||
gl.useProgram(program);
|
||||
|
||||
gl.bindTexture(gl.TEXTURE_2D, tex);
|
||||
|
||||
gl.uniform1i(gl.getUniformLocation(program, 'external_texture'), 0);
|
||||
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuff);
|
||||
gl.vertexAttribPointer(vloc, 2, gl.FLOAT, false, 0, 0);
|
||||
gl.enableVertexAttribArray(vloc);
|
||||
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, texBuff);
|
||||
gl.vertexAttribPointer(tloc, 2, gl.FLOAT, false, 0, 0);
|
||||
gl.enableVertexAttribArray(tloc);
|
||||
|
||||
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
|
||||
}
|
||||
|
||||
render(gl, gameView) {
|
||||
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
||||
gl.finish();
|
||||
|
||||
let render = () => {};
|
||||
gameView.animationFrame = requestAnimationFrame(render);
|
||||
}
|
||||
|
||||
createGameView = (canvas) => {
|
||||
this.canvas = canvas;
|
||||
const gl = this.canvas.getContext('webgl', {
|
||||
antialias: false,
|
||||
depth: false,
|
||||
stencil: false,
|
||||
alpha: false,
|
||||
desynchronized: true,
|
||||
failIfMajorPerformanceCaveat: false,
|
||||
});
|
||||
|
||||
const gameView = {
|
||||
canvas,
|
||||
gl,
|
||||
animationFrame: undefined,
|
||||
resize: (width, height) => {
|
||||
gl.viewport(0, 0, width, height);
|
||||
gl.canvas.width = width;
|
||||
gl.canvas.height = height;
|
||||
},
|
||||
};
|
||||
|
||||
this.createStuff(gl);
|
||||
|
||||
this.interval = setInterval(() => {
|
||||
this.render(gl, gameView);
|
||||
}, 0);
|
||||
|
||||
return gameView;
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this.canvas) {
|
||||
if (this.canvas.style.display != "none") {
|
||||
this.canvas.style.display = "none";
|
||||
}
|
||||
|
||||
if (this.interval) {
|
||||
clearInterval(this.interval);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
2
resources/[jobs]/[police]/[spy-bodycam]/spy-bodycam/web/js/jquery-3.7.1.min.js
vendored
Normal file
2
resources/[jobs]/[police]/[spy-bodycam]/spy-bodycam/web/js/jquery-3.7.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,451 @@
|
|||
@import url('https://fonts.googleapis.com/css2?family=Varela+Round&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Play:wght@400;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Fjalla+One&family=PT+Sans+Narrow:wght@400;700&family=Play:wght@400;700&display=swap');
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0%;
|
||||
padding: 0%;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.overlayCont{
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.overlayBody {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-top: 2rem;
|
||||
margin-right: 2rem;
|
||||
background: rgba(0, 0, 0, 0.486);
|
||||
box-shadow: -2px 0px 46px -9px rgba(0, 0, 0, 0.644) inset, 0px 4px 8px rgba(0, 0, 0, 0.418);
|
||||
padding: 1rem;
|
||||
border-radius: 10px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.RecordInfo{
|
||||
display: flex;
|
||||
margin-top: 10px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.recordBox{
|
||||
margin-top: 10px;
|
||||
margin-right: 2rem;
|
||||
font-family: "Play", sans-serif;
|
||||
background: rgba(0, 0, 0, 0.486);
|
||||
box-shadow: -2px 0px 46px -9px rgba(161, 2, 2, 0.644) inset, 0px 4px 8px rgba(0, 0, 0, 0.418);
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
font-size: 20px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.bodyInfobox {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
}
|
||||
.overlayBody img{
|
||||
width: 66px;
|
||||
height: auto;
|
||||
}
|
||||
.bodyNum , .bodyCallsign {
|
||||
font-family: "Play", sans-serif;
|
||||
}
|
||||
.bodyIcon{
|
||||
margin-right: 15px;
|
||||
color: rgb(173, 8, 8);
|
||||
}
|
||||
.bodyDate{
|
||||
font-size: 17px;
|
||||
font-family: "Varela Round", sans-serif;
|
||||
font-weight: bold;
|
||||
}
|
||||
.AddonIcons{
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
position: absolute;
|
||||
bottom: 9px;
|
||||
left: 20px;
|
||||
font-size: 15px;
|
||||
}
|
||||
.currWatchCont{
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
}
|
||||
|
||||
.watchInText{
|
||||
margin-left: 10px;
|
||||
}
|
||||
.watchIcon{
|
||||
font-size: 15px;
|
||||
}
|
||||
.userLaber {
|
||||
margin-top: 10px;
|
||||
margin-left: 10px;
|
||||
font-family: "Play", sans-serif;
|
||||
font-size: 20px;
|
||||
display: inline-block;
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
color: white;
|
||||
text-shadow: -1px 1px 1px rgba(0, 0, 0, 0.55);
|
||||
background: rgba(0, 0, 0, 0.397);
|
||||
box-shadow: -2px 0px 46px -9px rgba(0, 0, 0, 0.57) inset,0px 4px 8px rgba(0, 0, 0, 0.2);
|
||||
|
||||
}
|
||||
|
||||
.backInText{
|
||||
background: rgb(27, 27, 27);
|
||||
padding: 4px;
|
||||
border-radius: 5px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
.backIcon{
|
||||
font-size: 15px;
|
||||
}
|
||||
.backLabel {
|
||||
margin-top: 10px;
|
||||
margin-left: 10px;
|
||||
font-family: "Play", sans-serif;
|
||||
font-size: 20px;
|
||||
display: inline-block;
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
color: white;
|
||||
text-shadow: -1px 1px 1px rgba(0, 0, 0, 0.55);
|
||||
background: rgba(0, 0, 0, 0.397);
|
||||
box-shadow: -2px 0px 46px -9px rgba(0, 0, 0, 0.57) inset,0px 4px 8px rgba(0, 0, 0, 0.2);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@keyframes popIn {
|
||||
0% {
|
||||
transform: scale(0.8);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes popOut {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: scale(0.8);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.popIn {
|
||||
animation: popIn 0.5s forwards;
|
||||
}
|
||||
|
||||
.popOut {
|
||||
animation: popOut 0.5s forwards;
|
||||
}
|
||||
|
||||
.recCont{
|
||||
position: absolute;
|
||||
left: 10%;
|
||||
top: 10%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.recMain{
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 80vw;
|
||||
height: 80vh;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.recTitleTxt{
|
||||
font-family: "Bebas Neue", sans-serif;
|
||||
font-style: italic;
|
||||
font-size: 3rem;
|
||||
}
|
||||
.recTitle img{
|
||||
position: absolute;
|
||||
margin-top: 9px;
|
||||
margin-right: 20px;
|
||||
width: 100px;
|
||||
right: 0%;
|
||||
top: 0%;
|
||||
}
|
||||
.recTitle{
|
||||
padding-top: 10px;
|
||||
padding-left: 20px;
|
||||
padding-bottom: 0.5rem;
|
||||
background: #353b48;
|
||||
box-shadow: -2px 0px 46px -9px rgba(0, 0, 0, 0.644) inset, 0px 4px 8px rgba(0, 0, 0, 0.418);
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
}
|
||||
.recDesc{
|
||||
font-family: "PT Sans Narrow", sans-serif;
|
||||
font-style: italic;
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
.RecBox {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 63vh;
|
||||
background: #101522;
|
||||
box-shadow: -1px 15px 13px -1px rgba(0,0,0,0.5) inset, 0px -13px 13px -1px rgba(0,0,0,0.5) inset;
|
||||
}
|
||||
.recInnerScroll{
|
||||
display: flex;
|
||||
align-self: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
width: 100%;
|
||||
height: 55vh;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.recInnerScroll::-webkit-scrollbar {
|
||||
width: 3px;
|
||||
}
|
||||
.recInnerScroll::-webkit-scrollbar-thumb {
|
||||
background: #353b48;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.recInnerScroll::-webkit-scrollbar-thumb:hover {
|
||||
background: #242931;
|
||||
}
|
||||
|
||||
.camBox{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 90%;
|
||||
background: linear-gradient(180deg,#353b48 0%, #23272f 80%);
|
||||
box-shadow: 2px 2px 13px 7px rgba(0, 0, 0, 0.171) inset, -1px 7px 13px -4px rgba(0,0,0,0.42);
|
||||
border-radius: 3px;
|
||||
padding: 10px;
|
||||
border: 1px solid #c9cacc18;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.camShow:hover{
|
||||
color: #ffffff;
|
||||
text-shadow: 4px 1px 5px rgba(0, 0, 0, 0.315);
|
||||
}
|
||||
.camDelete:hover{
|
||||
color: #ca3333;
|
||||
text-shadow: 4px 1px 5px rgba(0, 0, 0, 0.315);
|
||||
}
|
||||
.camBox:hover{
|
||||
box-shadow: 2px 2px 13px 7px rgba(0, 0, 0, 0.411) inset,-1px 9px 13px -4px rgba(0,0,0,0.5);
|
||||
border: 1px solid #c9cacc3f;
|
||||
}
|
||||
.camIcons{
|
||||
color: #999999;
|
||||
display:flex ;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
font-size: 1.4rem;
|
||||
text-shadow: 4px 1px 5px rgba(0, 0, 0, 0.123);
|
||||
cursor: pointer;
|
||||
}
|
||||
.camDesc{
|
||||
font-family: "Play", sans-serif;
|
||||
}
|
||||
.camTitle{
|
||||
font-size: 1.4rem;
|
||||
font-family: "Fjalla One", sans-serif;
|
||||
}
|
||||
.camStreet{
|
||||
color: rgb(170, 170, 170);
|
||||
font-size: 0.9rem;
|
||||
font-family: "Fjalla One", sans-serif;
|
||||
font-style: italic;
|
||||
}
|
||||
.recFilters {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 5rem;
|
||||
padding: 0 1rem;
|
||||
color: white;
|
||||
border-bottom-left-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
background-color: #353b48;
|
||||
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.418);
|
||||
}
|
||||
|
||||
.recSearch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.searchText i {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.searchInput {
|
||||
height: 2rem;
|
||||
padding: 0.5rem;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
outline: none;
|
||||
background-color: #2f3640;
|
||||
color: white;
|
||||
font-size: 1rem;
|
||||
font-family: "Play", sans-serif;
|
||||
}
|
||||
|
||||
.recDateF {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 1rem;
|
||||
color: white;
|
||||
font-family: "Play", sans-serif;
|
||||
|
||||
}
|
||||
|
||||
.selectedDate {
|
||||
padding: 0.5rem;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
outline: none;
|
||||
background-color: #2f3640;
|
||||
font-size: 1rem;
|
||||
font-family: "Play", sans-serif;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.selectedDate::-webkit-calendar-picker-indicator {
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
.vidplaycont {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.vidMain {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: linear-gradient(180deg,#353b48 0%, #23272f 80%);
|
||||
border-radius: 5px;
|
||||
width: 63vw;
|
||||
height: 60vh;
|
||||
padding: 1rem;
|
||||
margin-top: 2rem;
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.vidPlayer {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.vidPlayerIcon {
|
||||
position: absolute;
|
||||
color: rgb(255, 255, 255);
|
||||
font-size: 1.5rem;
|
||||
top: 19px;
|
||||
right: 17px;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.vidPlayerIcon:hover {
|
||||
color: #ca3333;
|
||||
}
|
||||
|
||||
.askMain{
|
||||
position: absolute;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 80vw;
|
||||
height: 80vh;
|
||||
left: 10%;
|
||||
top: 10%;
|
||||
}
|
||||
.askIn{
|
||||
background: linear-gradient(180deg,#353b48 0%, #23272f 80%);
|
||||
box-shadow: 2px 2px 13px 7px rgba(0, 0, 0, 0.171) inset, -1px 7px 13px -4px rgba(0,0,0,0.42);
|
||||
border: 1px solid #c9cacc18;
|
||||
padding: 2rem;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.askIn:hover{
|
||||
box-shadow: 2px 2px 13px 7px rgba(0, 0, 0, 0.411) inset,-1px 9px 13px -4px rgba(0,0,0,0.5);
|
||||
border: 1px solid #c9cacc3f;
|
||||
}
|
||||
|
||||
.askIcon{
|
||||
font-size: 1.4rem;
|
||||
color: #ca3333;
|
||||
margin-bottom: 0.2rem;
|
||||
}
|
||||
|
||||
.askBtnCont{
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
width: 100%;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.askBtn{
|
||||
color: white;
|
||||
font-size: 1.4rem;
|
||||
font-family: "Play", sans-serif;
|
||||
background: #1015225d;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.askBtn:hover{
|
||||
background: #101522bb;
|
||||
}
|
||||
.askTitle{
|
||||
color: white;
|
||||
font-size: 1.4rem;
|
||||
font-family: "Fjalla One", sans-serif;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
.noRecAv{
|
||||
color: rgba(255, 255, 255, 0.26);
|
||||
font-size: 3rem;
|
||||
font-family: "Fjalla One", sans-serif;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
fx_version 'cerulean'
|
||||
|
||||
game 'gta5'
|
||||
lua54 'yes'
|
||||
author "aarjey0_0"
|
||||
|
||||
data_file 'DLC_ITYP_REQUEST' 'stream/rj_bodycam.ytyp'
|
Binary file not shown.
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue