This commit is contained in:
Hawk 2024-12-29 21:11:16 +01:00
parent d4cf6d9da4
commit 5d95c0182e
No known key found for this signature in database
GPG Key ID: 2890D5366F8BAC14
97 changed files with 33620 additions and 0 deletions

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Deltanic
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,72 @@
FiveM Freecam
=============
Simple freecam API for FiveM.
Features
--------
- Easy to use freecam API
- Improved state accuracy over native GTA
- Moves with the minimap
- Adjustable moving speed
- Support for keyboard and gamepads
- Fully configurable
Controls
--------
These are the default controls for the freecam. Keep in mind controls may be
different depending on your game settings or keyboard layout.
> Controls can be customized by [configuring the freecam](docs/CONFIGURING.md#control-mapping).
### Keyboard
- Mouse to look around
- W and S to move forward and backward
- A and D to move left and right
- Q and E to move up and down
- Alt to slow down
- Shift to speed up
### Gamepad
- Left joystick to move around
- Right joystick to look around
- Left button to move down
- Right button to move up
- Left trigger to slow down
- Right trigger to speed up
Usage
-----
In your `fxmanifest.lua`:
```lua
dependency 'fivem-freecam'
client_script 'script.lua'
```
In your `script.lua`:
```lua
local Freecam = exports['fivem-freecam']
-- Toggles the freecam by pressing F5
Citizen.CreateThread(function ()
while true do
Citizen.Wait(0)
if IsDisabledControlJustPressed(0, 166) then
local isActive = Freecam:IsActive()
Freecam:SetActive(not isActive)
end
end
end)
```
Documentation
-------------
- [Configuring](docs/CONFIGURING.md)
- [Functions](docs/EXPORTS.md)
- [Events](docs/EVENTS.md)

View File

@ -0,0 +1,139 @@
local _internal_camera = nil
local _internal_isFrozen = false
local _internal_pos = nil
local _internal_rot = nil
local _internal_fov = nil
local _internal_vecX = nil
local _internal_vecY = nil
local _internal_vecZ = nil
--------------------------------------------------------------------------------
function GetInitialCameraPosition()
if _G.CAMERA_SETTINGS.KEEP_POSITION and _internal_pos then
return _internal_pos
end
return GetGameplayCamCoord()
end
function GetInitialCameraRotation()
if _G.CAMERA_SETTINGS.KEEP_ROTATION and _internal_rot then
return _internal_rot
end
local rot = GetGameplayCamRot()
return vector3(rot.x, 0.0, rot.z)
end
--------------------------------------------------------------------------------
function IsFreecamFrozen()
return _internal_isFrozen
end
function SetFreecamFrozen(frozen)
local frozen = frozen == true
_internal_isFrozen = frozen
end
--------------------------------------------------------------------------------
function GetFreecamPosition()
return _internal_pos
end
function SetFreecamPosition(x, y, z)
local pos = vector3(x, y, z)
local int = GetInteriorAtCoords(pos)
LoadInterior(int)
SetFocusArea(pos)
LockMinimapPosition(x, y)
SetCamCoord(_internal_camera, pos)
_internal_pos = pos
end
--------------------------------------------------------------------------------
function GetFreecamRotation()
return _internal_rot
end
function SetFreecamRotation(x, y, z)
local rotX, rotY, rotZ = ClampCameraRotation(x, y, z)
local vecX, vecY, vecZ = EulerToMatrix(rotX, rotY, rotZ)
local rot = vector3(rotX, rotY, rotZ)
LockMinimapAngle(math.floor(rotZ))
SetCamRot(_internal_camera, rot)
_internal_rot = rot
_internal_vecX = vecX
_internal_vecY = vecY
_internal_vecZ = vecZ
end
--------------------------------------------------------------------------------
function GetFreecamFov()
return _internal_fov
end
function SetFreecamFov(fov)
local fov = Clamp(fov, 0.0, 90.0)
SetCamFov(_internal_camera, fov)
_internal_fov = fov
end
--------------------------------------------------------------------------------
function GetFreecamMatrix()
return _internal_vecX,
_internal_vecY,
_internal_vecZ,
_internal_pos
end
function GetFreecamTarget(distance)
local target = _internal_pos + (_internal_vecY * distance)
return target
end
--------------------------------------------------------------------------------
function IsFreecamActive()
return IsCamActive(_internal_camera) == 1
end
function SetFreecamActive(active)
if active == IsFreecamActive() then
return
end
local enableEasing = _G.CAMERA_SETTINGS.ENABLE_EASING
local easingDuration = _G.CAMERA_SETTINGS.EASING_DURATION
if active then
local pos = GetInitialCameraPosition()
local rot = GetInitialCameraRotation()
_internal_camera = CreateCam('DEFAULT_SCRIPTED_CAMERA', true)
SetFreecamFov(_G.CAMERA_SETTINGS.FOV)
SetFreecamPosition(pos.x, pos.y, pos.z)
SetFreecamRotation(rot.x, rot.y, rot.z)
TriggerEvent('freecam:onEnter')
else
DestroyCam(_internal_camera)
ClearFocus()
UnlockMinimapPosition()
UnlockMinimapAngle()
TriggerEvent('freecam:onExit')
end
SetPlayerControl(PlayerId(), not active)
RenderScriptCams(active, enableEasing, easingDuration)
end

View File

@ -0,0 +1,95 @@
local INPUT_LOOK_LR = 1
local INPUT_LOOK_UD = 2
local INPUT_CHARACTER_WHEEL = 19
local INPUT_SPRINT = 21
local INPUT_MOVE_UD = 31
local INPUT_MOVE_LR = 30
local INPUT_VEH_ACCELERATE = 71
local INPUT_VEH_BRAKE = 72
local INPUT_PARACHUTE_BRAKE_LEFT = 152
local INPUT_PARACHUTE_BRAKE_RIGHT = 153
--------------------------------------------------------------------------------
local BASE_CONTROL_MAPPING = protect({
-- Rotation
LOOK_X = INPUT_LOOK_LR,
LOOK_Y = INPUT_LOOK_UD,
-- Position
MOVE_X = INPUT_MOVE_LR,
MOVE_Y = INPUT_MOVE_UD,
MOVE_Z = { INPUT_PARACHUTE_BRAKE_LEFT, INPUT_PARACHUTE_BRAKE_RIGHT },
-- Multiplier
MOVE_FAST = INPUT_SPRINT,
MOVE_SLOW = INPUT_CHARACTER_WHEEL
})
--------------------------------------------------------------------------------
local BASE_CONTROL_SETTINGS = protect({
-- Rotation
LOOK_SENSITIVITY_X = 5,
LOOK_SENSITIVITY_Y = 5,
-- Position
BASE_MOVE_MULTIPLIER = 1,
FAST_MOVE_MULTIPLIER = 10,
SLOW_MOVE_MULTIPLIER = 10,
})
--------------------------------------------------------------------------------
local BASE_CAMERA_SETTINGS = protect({
--Camera
FOV = 45.0,
-- On enable/disable
ENABLE_EASING = true,
EASING_DURATION = 1000,
-- Keep position/rotation
KEEP_POSITION = false,
KEEP_ROTATION = false
})
--------------------------------------------------------------------------------
_G.KEYBOARD_CONTROL_MAPPING = table.copy(BASE_CONTROL_MAPPING)
_G.GAMEPAD_CONTROL_MAPPING = table.copy(BASE_CONTROL_MAPPING)
-- Swap up/down movement (LB for down, RB for up)
_G.GAMEPAD_CONTROL_MAPPING.MOVE_Z[1] = INPUT_PARACHUTE_BRAKE_LEFT
_G.GAMEPAD_CONTROL_MAPPING.MOVE_Z[2] = INPUT_PARACHUTE_BRAKE_RIGHT
-- Use LT and RT for speed
_G.GAMEPAD_CONTROL_MAPPING.MOVE_FAST = INPUT_VEH_ACCELERATE
_G.GAMEPAD_CONTROL_MAPPING.MOVE_SLOW = INPUT_VEH_BRAKE
protect(_G.KEYBOARD_CONTROL_MAPPING)
protect(_G.GAMEPAD_CONTROL_MAPPING)
--------------------------------------------------------------------------------
_G.KEYBOARD_CONTROL_SETTINGS = table.copy(BASE_CONTROL_SETTINGS)
_G.GAMEPAD_CONTROL_SETTINGS = table.copy(BASE_CONTROL_SETTINGS)
-- Gamepad sensitivity can be reduced by BASE.
_G.GAMEPAD_CONTROL_SETTINGS.LOOK_SENSITIVITY_X = 2
_G.GAMEPAD_CONTROL_SETTINGS.LOOK_SENSITIVITY_Y = 2
protect(_G.KEYBOARD_CONTROL_SETTINGS)
protect(_G.GAMEPAD_CONTROL_SETTINGS)
--------------------------------------------------------------------------------
_G.CAMERA_SETTINGS = table.copy(BASE_CAMERA_SETTINGS)
protect(_G.CAMERA_SETTINGS)
--------------------------------------------------------------------------------
-- Create some convenient variables.
-- Allows us to access controls and config without a gamepad switch.
_G.CONTROL_MAPPING = CreateGamepadMetatable(_G.KEYBOARD_CONTROL_MAPPING, _G.GAMEPAD_CONTROL_MAPPING)
_G.CONTROL_SETTINGS = CreateGamepadMetatable(_G.KEYBOARD_CONTROL_SETTINGS, _G.GAMEPAD_CONTROL_SETTINGS)

View File

@ -0,0 +1,29 @@
exports('IsActive', IsFreecamActive)
exports('SetActive', SetFreecamActive)
exports('IsFrozen', IsFreecamFrozen)
exports('SetFrozen', SetFreecamFrozen)
exports('GetFov', GetFreecamFov)
exports('SetFov', SetFreecamFov)
exports('GetPosition', GetFreecamPosition)
exports('SetPosition', SetFreecamPosition)
exports('GetRotation', GetFreecamRotation)
exports('SetRotation', SetFreecamRotation)
exports('GetMatrix', GetFreecamMatrix)
exports('GetTarget', GetFreecamTarget)
exports('GetPitch', function () return GetFreecamRotation().x end)
exports('GetRoll', function () return GetFreecamRotation().y end)
exports('GetYaw', function () return GetFreecamRotation().z end)
exports('GetKeyboardControl', function (key) return _G.KEYBOARD_CONTROL_MAPPING[key] end)
exports('GetGamepadControl', function (key) return _G.GAMEPAD_CONTROL_MAPPING[key] end)
exports('GetKeyboardSetting', function (key) return _G.KEYBOARD_CONTROL_SETTINGS[key] end)
exports('GetGamepadSetting', function (key) return _G.GAMEPAD_CONTROL_SETTINGS[key] end)
exports('GetCameraSetting', function (key) return _G.CAMERA_SETTINGS[key] end)
exports('SetKeyboardControl', function (key, value) _G.KEYBOARD_CONTROL_MAPPING[key] = value end)
exports('SetGamepadControl', function (key, value) _G.GAMEPAD_CONTROL_MAPPING[key] = value end)
exports('SetKeyboardSetting', function (key, value) _G.KEYBOARD_CONTROL_SETTINGS[key] = value end)
exports('SetGamepadSetting', function (key, value) _G.GAMEPAD_CONTROL_SETTINGS[key] = value end)
exports('SetCameraSetting', function (key, value) _G.CAMERA_SETTINGS[key] = value end)

View File

@ -0,0 +1,83 @@
local SETTINGS = _G.CONTROL_SETTINGS
local CONTROLS = _G.CONTROL_MAPPING
-------------------------------------------------------------------------------
local function GetSpeedMultiplier()
local fastNormal = GetSmartControlNormal(CONTROLS.MOVE_FAST)
local slowNormal = GetSmartControlNormal(CONTROLS.MOVE_SLOW)
local baseSpeed = SETTINGS.BASE_MOVE_MULTIPLIER
local fastSpeed = 1 + ((SETTINGS.FAST_MOVE_MULTIPLIER - 1) * fastNormal)
local slowSpeed = 1 + ((SETTINGS.SLOW_MOVE_MULTIPLIER - 1) * slowNormal)
local frameMultiplier = GetFrameTime() * 60
local speedMultiplier = baseSpeed * fastSpeed / slowSpeed
return speedMultiplier * frameMultiplier
end
local function UpdateCamera()
if not IsFreecamActive() or IsPauseMenuActive() then
return
end
if not IsFreecamFrozen() then
local vecX, vecY = GetFreecamMatrix()
local vecZ = vector3(0, 0, 1)
local pos = GetFreecamPosition()
local rot = GetFreecamRotation()
-- Get speed multiplier for movement
local speedMultiplier = GetSpeedMultiplier()
-- Get rotation input
local lookX = GetSmartControlNormal(CONTROLS.LOOK_X)
local lookY = GetSmartControlNormal(CONTROLS.LOOK_Y)
-- Get position input
local moveX = GetSmartControlNormal(CONTROLS.MOVE_X)
local moveY = GetSmartControlNormal(CONTROLS.MOVE_Y)
local moveZ = GetSmartControlNormal(CONTROLS.MOVE_Z)
-- Calculate new rotation.
local rotX = rot.x + (-lookY * SETTINGS.LOOK_SENSITIVITY_X)
local rotZ = rot.z + (-lookX * SETTINGS.LOOK_SENSITIVITY_Y)
local rotY = rot.y
-- Adjust position relative to camera rotation.
pos = pos + (vecX * moveX * speedMultiplier)
pos = pos + (vecY * -moveY * speedMultiplier)
pos = pos + (vecZ * moveZ * speedMultiplier)
-- Adjust new rotation
rot = vector3(rotX, rotY, rotZ)
-- Update camera
SetFreecamPosition(pos.x, pos.y, pos.z)
SetFreecamRotation(rot.x, rot.y, rot.z)
end
-- Trigger a tick event. Resources depending on the freecam position can
-- make use of this event.
TriggerEvent('freecam:onTick')
end
-------------------------------------------------------------------------------
Citizen.CreateThread(function ()
while true do
Citizen.Wait(0)
UpdateCamera()
end
end)
--------------------------------------------------------------------------------
-- When the resource is stopped, make sure to return the camera to the player.
AddEventHandler('onResourceStop', function (resourceName)
if resourceName == GetCurrentResourceName() then
SetFreecamActive(false)
end
end)

View File

@ -0,0 +1,91 @@
function table.copy(x)
local copy = {}
for k, v in pairs(x) do
if type(v) == 'table' then
copy[k] = table.copy(v)
else
copy[k] = v
end
end
return copy
end
function protect(t)
local fn = function (_, k)
error('Key `' .. tostring(k) .. '` is not supported.')
end
return setmetatable(t, {
__index = fn,
__newindex = fn
})
end
function CreateGamepadMetatable(keyboard, gamepad)
return setmetatable({}, {
__index = function (t, k)
local src = IsGamepadControl() and gamepad or keyboard
return src[k]
end
})
end
function Clamp(x, min, max)
return math.min(math.max(x, min), max)
end
function ClampCameraRotation(rotX, rotY, rotZ)
local x = Clamp(rotX, -90.0, 90.0)
local y = rotY % 360
local z = rotZ % 360
return x, y, z
end
function IsGamepadControl()
return not IsInputDisabled(2)
end
function GetSmartControlNormal(control)
if type(control) == 'table' then
local normal1 = GetDisabledControlNormal(0, control[1])
local normal2 = GetDisabledControlNormal(0, control[2])
return normal1 - normal2
end
return GetDisabledControlNormal(0, control)
end
function EulerToMatrix(rotX, rotY, rotZ)
local radX = math.rad(rotX)
local radY = math.rad(rotY)
local radZ = math.rad(rotZ)
local sinX = math.sin(radX)
local sinY = math.sin(radY)
local sinZ = math.sin(radZ)
local cosX = math.cos(radX)
local cosY = math.cos(radY)
local cosZ = math.cos(radZ)
local vecX = {}
local vecY = {}
local vecZ = {}
vecX.x = cosY * cosZ
vecX.y = cosY * sinZ
vecX.z = -sinY
vecY.x = cosZ * sinX * sinY - cosX * sinZ
vecY.y = cosX * cosZ - sinX * sinY * sinZ
vecY.z = cosY * sinX
vecZ.x = -cosX * cosZ * sinY + sinX * sinZ
vecZ.y = -cosZ * sinX + cosX * sinY * sinZ
vecZ.z = cosX * cosY
vecX = vector3(vecX.x, vecX.y, vecX.z)
vecY = vector3(vecY.x, vecY.y, vecY.z)
vecZ = vector3(vecZ.x, vecZ.y, vecZ.z)
return vecX, vecY, vecZ
end

View File

@ -0,0 +1,130 @@
Configuring
===========
Camera settings
---------------
The freecam accepts a few configuration values. These can be changed
programmatically like so:
```lua
local Freecam = exports['fivem-freecam']
Freecam:SetCameraSetting('EASING_DURATION', 2500)
```
Full list of camera settings and their default values:
```lua
--Camera
FOV = 45.0
-- On enable/disable
ENABLE_EASING = true
EASING_DURATION = 1000
-- Keep position/rotation
KEEP_POSITION = false
KEEP_ROTATION = false
```
> **Note:** FOV is applied upon entering the freecam. To change the FOV when
> the camera is already active, use [SetFov](EXPORTS.md#setfov).
- [SetCameraSetting](EXPORTS.md#setcamerasetting)
- [SetFov](EXPORTS.md#setfov)
Control mapping
---------------
It's possible to change the controls of the freecam. Controls are defined for
keyboards and gamepads and can be changed individually:
```lua
local Freecam = exports['fivem-freecam']
Freecam:SetKeyboardControl('MOVE_X', INPUT_MOVE_LR)
Freecam:SetGamepadControl('MOVE_Y', INPUT_MOVE_UD)
```
> Input names are taken from the [FiveM docs][fivem-docs].
> Use their corresponding control ID in your own code.
- [SetKeyboardControl](EXPORTS.md#setkeyboardcontrol)
- [SetGamepadControl](EXPORTS.md#setgamepadcontrol)
Adjustable **keyboard** mapping and their default controls:
```lua
-- Rotation
LOOK_X = INPUT_LOOK_LR
LOOK_Y = INPUT_LOOK_UD
-- Position
MOVE_X = INPUT_MOVE_LR
MOVE_Y = INPUT_MOVE_UD
MOVE_Z = { INPUT_PARACHUTE_BRAKE_LEFT, INPUT_PARACHUTE_BRAKE_RIGHT }
-- Multiplier
MOVE_FAST = INPUT_SPRINT
MOVE_SLOW = INPUT_CHARACTER_WHEEL
```
Adjustable **gamepad** mapping and their default controls:
```lua
-- Rotation
LOOK_X = INPUT_LOOK_LR
LOOK_Y = INPUT_LOOK_UD
-- Position
MOVE_X = INPUT_MOVE_LR
MOVE_Y = INPUT_MOVE_UD
MOVE_Z = { INPUT_PARACHUTE_BRAKE_RIGHT, INPUT_PARACHUTE_BRAKE_LEFT }
-- Multiplier
MOVE_FAST = INPUT_VEH_ACCELERATE
MOVE_SLOW = INPUT_VEH_BRAKE
```
Control settings
----------------
Control settings such as move speed multipliers and camera move sensitivity can
also be changed through settings:
```lua
local Freecam = exports['fivem-freecam']
Freecam:SetKeyboardSetting('LOOK_SENSITIVITY_X', 5)
Freecam:SetGamepadSetting('LOOK_SENSITIVITY_X', 2)
```
- [SetKeyboardSetting](EXPORTS.md#setkeyboardsetting)
- [SetGamepadSetting](EXPORTS.md#setgamepadsetting)
Adjustable **keyboard** settings and their default values:
```lua
-- Rotation
LOOK_SENSITIVITY_X = 5
LOOK_SENSITIVITY_Y = 5
-- Position
BASE_MOVE_MULTIPLIER = 1
FAST_MOVE_MULTIPLIER = 10
SLOW_MOVE_MULTIPLIER = 10
```
Adjustable **gamepad** settings and their default values:
```lua
-- Rotation
LOOK_SENSITIVITY_X = 2
LOOK_SENSITIVITY_Y = 2
-- Position
BASE_MOVE_MULTIPLIER = 1
FAST_MOVE_MULTIPLIER = 10
SLOW_MOVE_MULTIPLIER = 10
```
[fivem-docs]: https://docs.fivem.net/game-references/controls/

View File

@ -0,0 +1,49 @@
Events
======
`freecam:onEnter`
-----------------
Called upon entering the freecam after `Freecam:SetActive(true)`. Useful to
detect state changes of the freecam.
```lua
AddEventHandler('freecam:onEnter', function ()
-- Plays an effect upon entering the freecam.
StartScreenEffect('SuccessNeutral', 500, false)
PlaySoundFrontend(-1, 'Hit_In', 'PLAYER_SWITCH_CUSTOM_SOUNDSET', 1)
end)
```
`freecam:onExit`
----------------
Called upon exiting the freecam after `Freecam:SetActive(false)`. Useful to
detect state changes of the freecam.
```lua
AddEventHandler('freecam:onExit', function ()
-- Plays an effect upon exiting the freecam.
StartScreenEffect('SuccessNeutral', 500, false)
PlaySoundFrontend(-1, 'Hit_Out', 'PLAYER_SWITCH_CUSTOM_SOUNDSET', 1)
end)
```
`freecam:onTick`
----------------
Called every tick for as long as the freecam is active. Calls after any
positional or rotational updates so anything attached to the freecam stays
in sync. Not called when the freecam is inactive.
No values are passed to this event.
```lua
local Freecam = exports['fivem-freecam']
AddEventHandler('freecam:onTick', function ()
-- Gets the current target position of the freecam.
-- You could attach the player to this, or an object.
local target = Freecam:GetTarget(50)
print(target)
end)
```

View File

@ -0,0 +1,255 @@
Exports
=======
<!-- C# code blocks are used for convenient syntax highlighting. -->
Exported freecam functions.
> Examples assume you are referencing the freecam exports as such:
> ```lua
> local Freecam = exports['fivem-freecam']
> Freecam:SetActive(true)
> ```
### Getters
- [IsActive](#IsActive)
- [IsFrozen](#IsFrozen)
- [GetFov](#GetFov)
- [GetPosition](#GetPosition)
- [GetRotation](#GetRotation)
- [GetMatrix](#GetMatrix)
- [GetTarget](#GetTarget)
- [GetPitch](#GetPitch)
- [GetRoll](#GetRoll)
- [GetYaw](#GetYaw)
- [GetCameraSetting](#GetCameraSetting)
- [GetKeyboardSetting](#GetKeyboardSetting)
- [GetGamepadSetting](#GetGamepadSetting)
- [GetKeyboardControl](#GetKeyboardControl)
### Setters
- [SetActive](#SetActive)
- [SetFrozen](#SetFrozen)
- [SetFov](#SetFov)
- [SetPosition](#SetPosition)
- [SetRotation](#SetRotation)
- [SetCameraSetting](#SetCameraSetting)
- [SetKeyboardSetting](#SetKeyboardSetting)
- [SetGamepadSetting](#SetGamepadSetting)
- [SetKeyboardControl](#SetKeyboardControl)
- [SetGamepadControl](#SetGamepadControl)
---
`IsActive`
----------
Returns wether the freecam is currently active or not.
```c#
bool isActive = Freecam:IsActive()
```
`SetActive`
-----------
Enters or exits the freecam.
```c#
void Freecam:SetActive(bool active)
```
`IsFrozen`
----------
Returns wether the freecam position is currently frozen.
```c#
bool isFrozen = Freecam:IsFrozen()
```
`SetFrozen`
-----------
Sets the freecam frozen. When frozen, controls do not update the position or
rotation anymore but [SetPosition](#setposition)/[SetRotation](#setrotation) will.
```c#
void Freecam:SetFrozen(bool frozen)
```
`GetFov`
--------
Returns the field of view of the freecam.
```c#
float fov = Freecam:GetFov()
```
`SetFov`
--------
Sets the current field of view of the freecam. This does NOT update the default
FOV for the freecam. Use [SetCameraSetting](#setcamerasetting) for that.
```c#
void Freecam:SetFov(float fov)
```
`GetPosition`
-------------
Returns the current position of the freecam.
```c#
vector3 position = Freecam:GetPosition()
```
`SetPosition`
-------------
Sets a new position for the freecam.
```c#
void Freecam:SetPosition(float posX, float posY, float posZ)
```
`GetRotation`
-------------
Returns the current rotation of the freecam.
```c#
vector3 rotation = Freecam:GetRotation()
```
`SetRotation`
-------------
Sets a new position for the freecam.
```c#
void Freecam:SetRotation(float rotX, float rotY, float rotZ)
```
`GetMatrix`
-----------
Returns the current view matrix of the freecam.
```c#
vector3 vecX, vector3 vecY, vector3 vecZ, vector3 pos = Freecam:GetMatrix()
```
`GetTarget`
-----------
Returns the position the freecam is looking at from the given distance.
```c#
vector3 target = Freecam:GetTarget(float distance)
```
`GetPitch`
----------
Returns the current pitch (rotX) of the freecam.
```c#
float pitch = Freecam:GetPitch()
```
`GetRoll`
---------
Returns the current roll (rotY) of the freecam.
```c#
float roll = Freecam:GetRoll()
```
`GetYaw`
--------
Returns the current yaw (rotZ) of the freecam.
```c#
float yaw = Freecam:GetYaw()
```
`GetCameraSetting`
------------------
Returns the value of a camera setting.
See [CONFIGURING](CONFIGURING.md#camera-settings) for details.
```c#
mixed value = Freecam:GetCameraSetting(string key)
```
`SetCameraSetting`
------------------
Sets the value of a camera setting.
See [CONFIGURING](CONFIGURING.md#camera-settings) for details.
```c#
void Freecam:SetCameraSetting(string key, mixed value)
```
`GetKeyboardSetting`
--------------------
Returns the value of a keyboard setting.
See [CONFIGURING](CONFIGURING.md#control-settings) for details.
```c#
mixed value = Freecam:GetKeyboardSetting(string key)
```
`SetKeyboardSetting`
--------------------
Sets the value of a keyboard setting.
See [CONFIGURING](CONFIGURING.md#control-settings) for details.
```c#
void Freecam:SetKeyboardSetting(string key, mixed value)
```
`GetGamepadSetting`
-------------------
Returns the value of a gamepad setting.
See [CONFIGURING](CONFIGURING.md#control-settings) for details.
```c#
mixed value = Freecam:GetGamepadSetting(string key)
```
`SetGamepadSetting`
-------------------
Sets the value of a gamepad setting.
See [CONFIGURING](CONFIGURING.md#control-settings) for details.
```c#
void Freecam:SetGamepadSetting(string key, mixed value)
```
`GetKeyboardControl`
--------------------
Returns the value of a keyboard control.
See [CONFIGURING](CONFIGURING.md#control-mapping) for details.
```c#
mixed value = Freecam:GetKeyboardControl(string key)
```
`SetKeyboardControl`
--------------------
Sets the value of a keyboard control.
See [CONFIGURING](CONFIGURING.md#control-mapping) for details.
```c#
void Freecam:SetKeyboardControl(string key, int value)
void Freecam:SetKeyboardControl(string key, table value)
```
`GetGamepadControl`
-------------------
Returns the value of a gamepad control.
See [CONFIGURING](CONFIGURING.md#control-mapping) for details.
```c#
mixed value = Freecam:GetGamepadControl(string key)
```
`SetGamepadControl`
-------------------
Sets the value of a gamepad control.
See [CONFIGURING](CONFIGURING.md#control-mapping) for details.
```c#
void Freecam:SetGamepadControl(string key, int value)
void Freecam:SetGamepadControl(string key, table value)
```

View File

@ -0,0 +1,12 @@
fx_version 'cerulean'
games { 'gta5' }
author 'Deltanic'
description 'Simple freecam API for FiveM.'
version '1.0.0'
client_script 'client/utils.lua'
client_script 'client/config.lua'
client_script 'client/camera.lua'
client_script 'client/exports.lua'
client_script 'client/main.lua'

View File

@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto

View File

@ -0,0 +1,26 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.

View File

@ -0,0 +1,458 @@
Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)
https://creativecommons.org/licenses/by-nc-sa/4.0/
This is a human-readable summary of (and not a substitute for) the license. Disclaimer.
You are free to:
Share — copy and redistribute the material in any medium or format
Adapt — remix, transform, and build upon the material
The licensor cannot revoke these freedoms as long as you follow the license terms.
Under the following terms:
Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
NonCommercial — You may not use the material for commercial purposes.
ShareAlike — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original.
No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits.
Notices:
You do not have to comply with the license for elements of the material in the public domain or where your use is permitted by an applicable exception or limitation.
No warranties are given. The license may not give you all of the permissions necessary for your intended use. For example, other rights such as publicity, privacy, or moral rights may limit how you use the material.
Creative Commons Corporation ("Creative Commons") is not a law firm and
does not provide legal services or legal advice. Distribution of
Creative Commons public licenses does not create a lawyer-client or
other relationship. Creative Commons makes its licenses and related
information available on an "as-is" basis. Creative Commons gives no
warranties regarding its licenses, any material licensed under their
terms and conditions, or any related information. Creative Commons
disclaims all liability for damages resulting from their use to the
fullest extent possible.
Using Creative Commons Public Licenses
Creative Commons public licenses provide a standard set of terms and
conditions that creators and other rights holders may use to share
original works of authorship and other material subject to copyright
and certain other rights specified in the public license below. The
following considerations are for informational purposes only, are not
exhaustive, and do not form part of our licenses.
Considerations for licensors: Our public licenses are
intended for use by those authorized to give the public
permission to use material in ways otherwise restricted by
copyright and certain other rights. Our licenses are
irrevocable. Licensors should read and understand the terms
and conditions of the license they choose before applying it.
Licensors should also secure all rights necessary before
applying our licenses so that the public can reuse the
material as expected. Licensors should clearly mark any
material not subject to the license. This includes other CC-
licensed material, or material used under an exception or
limitation to copyright. More considerations for licensors:
wiki.creativecommons.org/Considerations_for_licensors
Considerations for the public: By using one of our public
licenses, a licensor grants the public permission to use the
licensed material under specified terms and conditions. If
the licensor's permission is not necessary for any reason--for
example, because of any applicable exception or limitation to
copyright--then that use is not regulated by the license. Our
licenses grant only permissions under copyright and certain
other rights that a licensor has authority to grant. Use of
the licensed material may still be restricted for other
reasons, including because others have copyright or other
rights in the material. A licensor may make special requests,
such as asking that all changes be marked or described.
Although not required by our licenses, you are encouraged to
respect those requests where reasonable. More considerations
for the public:
wiki.creativecommons.org/Considerations_for_licensees
=======================================================================
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
Public License
By exercising the Licensed Rights (defined below), You accept and agree
to be bound by the terms and conditions of this Creative Commons
Attribution-NonCommercial-ShareAlike 4.0 International Public License
("Public License"). To the extent this Public License may be
interpreted as a contract, You are granted the Licensed Rights in
consideration of Your acceptance of these terms and conditions, and the
Licensor grants You such rights in consideration of benefits the
Licensor receives from making the Licensed Material available under
these terms and conditions.
Section 1 -- Definitions.
a. Adapted Material means material subject to Copyright and Similar
Rights that is derived from or based upon the Licensed Material
and in which the Licensed Material is translated, altered,
arranged, transformed, or otherwise modified in a manner requiring
permission under the Copyright and Similar Rights held by the
Licensor. For purposes of this Public License, where the Licensed
Material is a musical work, performance, or sound recording,
Adapted Material is always produced where the Licensed Material is
synched in timed relation with a moving image.
b. Adapter's License means the license You apply to Your Copyright
and Similar Rights in Your contributions to Adapted Material in
accordance with the terms and conditions of this Public License.
c. BY-NC-SA Compatible License means a license listed at
creativecommons.org/compatiblelicenses, approved by Creative
Commons as essentially the equivalent of this Public License.
d. Copyright and Similar Rights means copyright and/or similar rights
closely related to copyright including, without limitation,
performance, broadcast, sound recording, and Sui Generis Database
Rights, without regard to how the rights are labeled or
categorized. For purposes of this Public License, the rights
specified in Section 2(b)(1)-(2) are not Copyright and Similar
Rights.
e. Effective Technological Measures means those measures that, in the
absence of proper authority, may not be circumvented under laws
fulfilling obligations under Article 11 of the WIPO Copyright
Treaty adopted on December 20, 1996, and/or similar international
agreements.
f. Exceptions and Limitations means fair use, fair dealing, and/or
any other exception or limitation to Copyright and Similar Rights
that applies to Your use of the Licensed Material.
g. License Elements means the license attributes listed in the name
of a Creative Commons Public License. The License Elements of this
Public License are Attribution, NonCommercial, and ShareAlike.
h. Licensed Material means the artistic or literary work, database,
or other material to which the Licensor applied this Public
License.
i. Licensed Rights means the rights granted to You subject to the
terms and conditions of this Public License, which are limited to
all Copyright and Similar Rights that apply to Your use of the
Licensed Material and that the Licensor has authority to license.
j. Licensor means the individual(s) or entity(ies) granting rights
under this Public License.
k. NonCommercial means not primarily intended for or directed towards
commercial advantage or monetary compensation. For purposes of
this Public License, the exchange of the Licensed Material for
other material subject to Copyright and Similar Rights by digital
file-sharing or similar means is NonCommercial provided there is
no payment of monetary compensation in connection with the
exchange.
l. Share means to provide material to the public by any means or
process that requires permission under the Licensed Rights, such
as reproduction, public display, public performance, distribution,
dissemination, communication, or importation, and to make material
available to the public including in ways that members of the
public may access the material from a place and at a time
individually chosen by them.
m. Sui Generis Database Rights means rights other than copyright
resulting from Directive 96/9/EC of the European Parliament and of
the Council of 11 March 1996 on the legal protection of databases,
as amended and/or succeeded, as well as other essentially
equivalent rights anywhere in the world.
n. You means the individual or entity exercising the Licensed Rights
under this Public License. Your has a corresponding meaning.
Section 2 -- Scope.
a. License grant.
1. Subject to the terms and conditions of this Public License,
the Licensor hereby grants You a worldwide, royalty-free,
non-sublicensable, non-exclusive, irrevocable license to
exercise the Licensed Rights in the Licensed Material to:
a. reproduce and Share the Licensed Material, in whole or
in part, for NonCommercial purposes only; and
b. produce, reproduce, and Share Adapted Material for
NonCommercial purposes only.
2. Exceptions and Limitations. For the avoidance of doubt, where
Exceptions and Limitations apply to Your use, this Public
License does not apply, and You do not need to comply with
its terms and conditions.
3. Term. The term of this Public License is specified in Section
6(a).
4. Media and formats; technical modifications allowed. The
Licensor authorizes You to exercise the Licensed Rights in
all media and formats whether now known or hereafter created,
and to make technical modifications necessary to do so. The
Licensor waives and/or agrees not to assert any right or
authority to forbid You from making technical modifications
necessary to exercise the Licensed Rights, including
technical modifications necessary to circumvent Effective
Technological Measures. For purposes of this Public License,
simply making modifications authorized by this Section 2(a)
(4) never produces Adapted Material.
5. Downstream recipients.
a. Offer from the Licensor -- Licensed Material. Every
recipient of the Licensed Material automatically
receives an offer from the Licensor to exercise the
Licensed Rights under the terms and conditions of this
Public License.
b. Additional offer from the Licensor -- Adapted Material.
Every recipient of Adapted Material from You
automatically receives an offer from the Licensor to
exercise the Licensed Rights in the Adapted Material
under the conditions of the Adapter's License You apply.
c. No downstream restrictions. You may not offer or impose
any additional or different terms or conditions on, or
apply any Effective Technological Measures to, the
Licensed Material if doing so restricts exercise of the
Licensed Rights by any recipient of the Licensed
Material.
6. No endorsement. Nothing in this Public License constitutes or
may be construed as permission to assert or imply that You
are, or that Your use of the Licensed Material is, connected
with, or sponsored, endorsed, or granted official status by,
the Licensor or others designated to receive attribution as
provided in Section 3(a)(1)(A)(i).
b. Other rights.
1. Moral rights, such as the right of integrity, are not
licensed under this Public License, nor are publicity,
privacy, and/or other similar personality rights; however, to
the extent possible, the Licensor waives and/or agrees not to
assert any such rights held by the Licensor to the limited
extent necessary to allow You to exercise the Licensed
Rights, but not otherwise.
2. Patent and trademark rights are not licensed under this
Public License.
3. To the extent possible, the Licensor waives any right to
collect royalties from You for the exercise of the Licensed
Rights, whether directly or through a collecting society
under any voluntary or waivable statutory or compulsory
licensing scheme. In all other cases the Licensor expressly
reserves any right to collect such royalties, including when
the Licensed Material is used other than for NonCommercial
purposes.
Section 3 -- License Conditions.
Your exercise of the Licensed Rights is expressly made subject to the
following conditions.
a. Attribution.
1. If You Share the Licensed Material (including in modified
form), You must:
a. retain the following if it is supplied by the Licensor
with the Licensed Material:
i. identification of the creator(s) of the Licensed
Material and any others designated to receive
attribution, in any reasonable manner requested by
the Licensor (including by pseudonym if
designated);
ii. a copyright notice;
iii. a notice that refers to this Public License;
iv. a notice that refers to the disclaimer of
warranties;
v. a URI or hyperlink to the Licensed Material to the
extent reasonably practicable;
b. indicate if You modified the Licensed Material and
retain an indication of any previous modifications; and
c. indicate the Licensed Material is licensed under this
Public License, and include the text of, or the URI or
hyperlink to, this Public License.
2. You may satisfy the conditions in Section 3(a)(1) in any
reasonable manner based on the medium, means, and context in
which You Share the Licensed Material. For example, it may be
reasonable to satisfy the conditions by providing a URI or
hyperlink to a resource that includes the required
information.
3. If requested by the Licensor, You must remove any of the
information required by Section 3(a)(1)(A) to the extent
reasonably practicable.
b. ShareAlike.
In addition to the conditions in Section 3(a), if You Share
Adapted Material You produce, the following conditions also apply.
1. The Adapter's License You apply must be a Creative Commons
license with the same License Elements, this version or
later, or a BY-NC-SA Compatible License.
2. You must include the text of, or the URI or hyperlink to, the
Adapter's License You apply. You may satisfy this condition
in any reasonable manner based on the medium, means, and
context in which You Share Adapted Material.
3. You may not offer or impose any additional or different terms
or conditions on, or apply any Effective Technological
Measures to, Adapted Material that restrict exercise of the
rights granted under the Adapter's License You apply.
Section 4 -- Sui Generis Database Rights.
Where the Licensed Rights include Sui Generis Database Rights that
apply to Your use of the Licensed Material:
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
to extract, reuse, reproduce, and Share all or a substantial
portion of the contents of the database for NonCommercial purposes
only;
b. if You include all or a substantial portion of the database
contents in a database in which You have Sui Generis Database
Rights, then the database in which You have Sui Generis Database
Rights (but not its individual contents) is Adapted Material,
including for purposes of Section 3(b); and
c. You must comply with the conditions in Section 3(a) if You Share
all or a substantial portion of the contents of the database.
For the avoidance of doubt, this Section 4 supplements and does not
replace Your obligations under this Public License where the Licensed
Rights include other Copyright and Similar Rights.
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
c. The disclaimer of warranties and limitation of liability provided
above shall be interpreted in a manner that, to the extent
possible, most closely approximates an absolute disclaimer and
waiver of all liability.
Section 6 -- Term and Termination.
a. This Public License applies for the term of the Copyright and
Similar Rights licensed here. However, if You fail to comply with
this Public License, then Your rights under this Public License
terminate automatically.
b. Where Your right to use the Licensed Material has terminated under
Section 6(a), it reinstates:
1. automatically as of the date the violation is cured, provided
it is cured within 30 days of Your discovery of the
violation; or
2. upon express reinstatement by the Licensor.
For the avoidance of doubt, this Section 6(b) does not affect any
right the Licensor may have to seek remedies for Your violations
of this Public License.
c. For the avoidance of doubt, the Licensor may also offer the
Licensed Material under separate terms or conditions or stop
distributing the Licensed Material at any time; however, doing so
will not terminate this Public License.
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
License.
Section 7 -- Other Terms and Conditions.
a. The Licensor shall not be bound by any additional or different
terms or conditions communicated by You unless expressly agreed.
b. Any arrangements, understandings, or agreements regarding the
Licensed Material not stated herein are separate from and
independent of the terms and conditions of this Public License.
Section 8 -- Interpretation.
a. For the avoidance of doubt, this Public License does not, and
shall not be interpreted to, reduce, limit, restrict, or impose
conditions on any use of the Licensed Material that could lawfully
be made without permission under this Public License.
b. To the extent possible, if any provision of this Public License is
deemed unenforceable, it shall be automatically reformed to the
minimum extent necessary to make it enforceable. If the provision
cannot be reformed, it shall be severed from this Public License
without affecting the enforceability of the remaining terms and
conditions.
c. No term or condition of this Public License will be waived and no
failure to comply consented to unless expressly agreed to by the
Licensor.
d. Nothing in this Public License constitutes or may be interpreted
as a limitation upon, or waiver of, any privileges and immunities
that apply to the Licensor or You, including from the legal
processes of any jurisdiction or authority.
=======================================================================
Creative Commons is not a party to its public
licenses. Notwithstanding, Creative Commons may elect to apply one of
its public licenses to material it publishes and in those instances
will be considered the “Licensor.” The text of the Creative Commons
public licenses is dedicated to the public domain under the CC0 Public
Domain Dedication. Except for the limited purpose of indicating that
material is shared under a Creative Commons public license or as
otherwise permitted by the Creative Commons policies published at
creativecommons.org/policies, Creative Commons does not authorize the
use of the trademark "Creative Commons" or any other trademark or logo
of Creative Commons without its prior written consent including,
without limitation, in connection with any unauthorized modifications
to any of its public licenses or any other arrangements,
understandings, or agreements concerning use of licensed material. For
the avoidance of doubt, this paragraph does not form part of the
public licenses.
Creative Commons may be contacted at creativecommons.org.

View File

@ -0,0 +1,139 @@
# ps-adminmenu
The Admin Menu crafted by [OK1ez](https://github.com/OK1ez) and our dedicated team is user-friendly and intuitive. We invite you to contribute by submitting new features through PRs. We're always eager to review and consider new features. Make sure you use our template when opening Issues or they will be auto closed.
## Unofficial ESX Version
Made by Avilchiis for the community, you can download it [here](https://github.com/avilchiis/ps-adminmenu). **WE DO NOT PROVIDE SUPPORT FOR ESX VERSION, DO NOT ASK YOU'LL BE IGNORED.**
# Preview
<img src="https://github.com/Project-Sloth/ps-adminmenu/assets/82112471/0da6cf4d-fc72-497f-a59c-08011b3785ab" width="300">
<img src="https://github.com/Project-Sloth/ps-adminmenu/assets/82112471/2d366445-4094-4a10-a570-265cb230fc37" width="300">
<img src="https://github.com/Project-Sloth/ps-adminmenu/assets/82112471/33382d64-3b95-42aa-9659-d92dbdca94d2" width="600">
<img src="https://github.com/Project-Sloth/ps-adminmenu/assets/82112471/d63982c6-9b04-4dec-b059-55e1cc5ea608" width="600">
<img src="https://github.com/Project-Sloth/ps-adminmenu/assets/82112471/ab44df15-7d9e-4753-9c71-2492348a229d" width="600">
<img src="https://github.com/Project-Sloth/ps-adminmenu/assets/82112471/0fb81425-dd45-4354-8fb7-94e62ac954ae" width="600">
<img src="https://github.com/Project-Sloth/ps-adminmenu/assets/82112471/6f1d0ea9-ea55-4088-98de-ceb4fb1c3838" width="600">
# Change Language.
- Place this `setr ox:locale en` inside your `server.cfg`
- Change the `en` to your desired language!
**Supported Languages:**
| **Alias** | **Language Names** |
|--------------|---------------|
|en |English |
|fr |French |
|id |Indonesia |
|pt-br |Brazilian Portuguese |
|tr |Turkish |
|es |Spanish |
|nl |Dutch |
|no |Norwegian |
# Features
* Admin Car
* Ban Player
* Bring Player
* Change Plate
* Checking number plates before ```Change Plate```
* Change Time
* Change Weather
* Check Permissions
* Clear Inventory
* Clear Inventory Offline
* Clothing Menu
* Copy Coordinates
* Delete Vehicle
* Delete Laser
* Explode Player
* Fix Vehicle
* Freeze Player
* Give Clothing Menu
* Give Item
* Give Item to All
* Give Money
* Give Money to All
* Give Vehicle to Player
* Give NUI Focus
* God Mode
* Invisible
* Infinite Ammo
* Kick Player
* Kill Player
* Make Player Drunk
* Message Player
* Mute Player
* Max Vehicle Mods
* No Clip
* Open Inventory
* Open Stash
* Open Trunk
* Play Sound
* Refuel Vehicle
* Remove Money
* Remove Stress
* Revive All
* Revive Player
* Revive Radius
* Set Bucket
* Server Announcement
* Set Ammo
* Set Vehicle State in Garage (In & Out)
* Set Gang
* Set Job
* Set on Fire
* Set Permissions
* Set Player Ped
* Sit in Vehicle
* Spawn Vehicle
* Spectate Player
* Teleport Back
* Teleport to Coordinates
* Teleport to Marker
* Teleport to player
* Toggle Blackout
* Toggle Blips
* Toggle Coords
* Toggle Cuffs
* Toggle Delete Laser
* Toggle Duty
* Toggle Names
* Vehicle Dev Menu
* Warn player
# Depedency
1. [qb-core](https://github.com/qbcore-framework/qb-core)
2. [ox_lib](https://github.com/overextended/ox_lib)
# Installation
1. Download the latest release.
2. Add the files to your server resources.
3. Ensure `ps-adminmenu` in your server cfg. Make sure ox_lib starts before ps-adminmenu.
4. Set the config in `shared/config.lua` to your needs.
A community video has been made for setup instructions and showcase, you can find it [here](https://www.youtube.com/watch?v=aez5RIi8db8&ab_channel=Kamaryn)
## Permissions
Make sure you've correctly configured player permissions in your server.cfg by using ACE permissions with the appropriate identifier. Otherwise, you'll be unable to access or launch the admin menu. Here's a sample configuration where the player, MonkeyWhisper, is assigned god, admin, and mod roles, you should not have all 3 permissions for a single person. For a deeper understanding of how QBCore manages permissions, refer to [this documentation.](https://docs.qbcore.org/qbcore-documentation/guides/setting-permissions)
### Player Permission
```
add_principal identifier.fivem:565139 qbcore.god # MonkeyWhisper
add_principal identifier.fivem:565139 qbcore.admin # MonkeyWhisper
add_principal identifier.fivem:565139 qbcore.mod # MonkeyWhisper
```
## Setting Up Logs
1. Set up a Discord Webhook for the channel you want the logs to be.
2. Add this to `qb-smallresource/server/logs.lua` -
`['ps-adminmenu'] = 'discord webhook'`
3. Replace the place holder with your webhook link.
# To Do
* Rework the blips/names
# Credits
* [OK1ez](https://github.com/OK1ez)
* [Lenzh](https://github.com/Lenzh)
* [LeSiiN](https://github.com/LeSiiN)
* Project Sloth Team

View File

@ -0,0 +1,28 @@
local function getMessagesCallBack()
return lib.callback.await('ps-adminmenu:callback:GetMessages', false)
end
RegisterNUICallback("GetMessages", function(_, cb)
local data = getMessagesCallBack()
if next(data) then
SendNUIMessage({
action = "setMessages",
data = data
})
end
cb(1)
end)
RegisterNUICallback("SendMessage", function(msgData, cb)
local message = msgData.message
print(message, PlayerData.citizenid, PlayerData.charinfo.firstname .. " " .. PlayerData.charinfo.lastname )
TriggerServerEvent("ps-adminmenu:server:sendMessageServer", message, PlayerData.citizenid, PlayerData.charinfo.firstname .. " " .. PlayerData.charinfo.lastname)
local data = getMessagesCallBack()
SendNUIMessage({
action = "setMessages",
data = data
})
cb(1)
end)

View File

@ -0,0 +1,88 @@
local PedList = require "data.ped"
-- Returns a list of vehicles from QBCore.Shared.Vehicles
local function GetVehicles()
local vehicles = {}
for _, v in pairs(QBCore.Shared.Vehicles) do
vehicles[#vehicles + 1] = { label = v.name, value = v.model }
end
return vehicles
end
-- Returns a list of items from QBCore.Shared.Items
local function GetItems()
local items = {}
local ItemsData = QBCore.Shared.Items
if Config.Inventory == "ox_inventory" then
ItemsData = exports.ox_inventory:Items()
end
for name, v in pairs(ItemsData) do
items[#items + 1] = { label = v.label, value = name }
end
return items
end
-- Returns a list of jobs from QBCore.Shared.Jobs
local function GetJobs()
local jobs = {}
for name, v in pairs(QBCore.Shared.Jobs) do
local gradeDataList = {}
for grade, gradeData in pairs(v.grades) do
gradeDataList[#gradeDataList + 1] = { name = gradeData.name, grade = grade, isboss = gradeData.isboss }
end
jobs[#jobs + 1] = { label = v.label, value = name, grades = gradeDataList }
end
return jobs
end
-- Returns a list of gangs from QBCore.Shared.Gangs
local function GetGangs()
local gangs = {}
for name, v in pairs(QBCore.Shared.Gangs) do
local gradeDataList = {}
for grade, gradeData in pairs(v.grades) do
gradeDataList[#gradeDataList + 1] = { name = gradeData.name, grade = grade, isboss = gradeData.isboss }
end
gangs[#gangs + 1] = { label = v.label, value = name, grades = gradeDataList }
end
return gangs
end
-- Returns a list of locations from QBCore.Shared.Loactions
local function GetLocations()
local locations = {}
for name, v in pairs(QBCore.Shared.Locations) do
locations[#locations + 1] = { label = name, value = v }
end
return locations
end
-- Sends data to the UI on resource start
function GetData()
SendNUIMessage({
action = "data",
data = {
vehicles = GetVehicles(),
items = GetItems(),
jobs = GetJobs(),
gangs = GetGangs(),
locations = GetLocations(),
pedlist = PedList
},
})
end

View File

@ -0,0 +1,231 @@
local dickheaddebug = false
local Keys = {
["ESC"] = 322, ["F1"] = 288, ["F2"] = 289, ["F3"] = 170, ["F5"] = 166, ["F6"] = 167, ["F7"] = 168, ["F8"] = 169, ["F9"] = 56, ["F10"] = 57,
["~"] = 243, ["1"] = 157, ["2"] = 158, ["3"] = 160, ["4"] = 164, ["5"] = 165, ["6"] = 159, ["7"] = 161, ["8"] = 162, ["9"] = 163, ["-"] = 84, ["="] = 83, ["BACKSPACE"] = 177,
["TAB"] = 37, ["Q"] = 44, ["W"] = 32, ["E"] = 38, ["R"] = 45, ["T"] = 245, ["Y"] = 246, ["U"] = 303, ["P"] = 199, ["["] = 39, ["]"] = 40, ["ENTER"] = 18,
["CAPS"] = 137, ["A"] = 34, ["S"] = 8, ["D"] = 9, ["F"] = 23, ["G"] = 47, ["H"] = 74, ["K"] = 311, ["L"] = 182,
["LEFTSHIFT"] = 21, ["Z"] = 20, ["X"] = 73, ["C"] = 26, ["V"] = 0, ["B"] = 29, ["N"] = 249, ["M"] = 244, [","] = 82, ["."] = 81,
["LEFTCTRL"] = 36, ["LEFTALT"] = 19, ["SPACE"] = 22, ["RIGHTCTRL"] = 70,
["HOME"] = 213, ["PAGEUP"] = 10, ["PAGEDOWN"] = 11, ["DELETE"] = 178,
["LEFT"] = 174, ["RIGHT"] = 175, ["TOP"] = 27, ["DOWN"] = 173,
["NENTER"] = 201, ["N4"] = 108, ["N5"] = 60, ["N6"] = 107, ["N+"] = 96, ["N-"] = 97, ["N7"] = 117, ["N8"] = 61, ["N9"] = 118
}
RegisterNetEvent("hud:enabledebug")
AddEventHandler("hud:enabledebug",function()
if not CheckPerms(Config.Actions["noclip"].perms) then return end
dickheaddebug = not dickheaddebug
if dickheaddebug then
print("Debug: Enabled")
else
print("Debug: Disabled")
end
end)
local inFreeze = false
local lowGrav = false
function drawTxt(x,y ,width,height,scale, text, r,g,b,a)
SetTextFont(0)
SetTextProportional(0)
SetTextScale(0.25, 0.25)
SetTextColour(r, g, b, a)
SetTextDropShadow(0, 0, 0, 0,255)
SetTextEdge(1, 0, 0, 0, 255)
SetTextDropShadow()
SetTextOutline()
SetTextEntry("STRING")
AddTextComponentString(text)
DrawText(x - width/2, y - height/2 + 0.005)
end
function DrawText3Ds(x,y,z, text)
local onScreen,_x,_y=World3dToScreen2d(x,y,z)
local px,py,pz=table.unpack(GetGameplayCamCoords())
SetTextScale(0.35, 0.35)
SetTextFont(4)
SetTextProportional(1)
SetTextColour(255, 255, 255, 215)
SetTextEntry("STRING")
SetTextCentre(1)
AddTextComponentString(text)
DrawText(_x,_y)
local factor = (string.len(text)) / 370
DrawRect(_x,_y+0.0125, 0.015+ factor, 0.03, 41, 11, 41, 68)
end
function GetVehicle()
local playerped = GetPlayerPed(-1)
local playerCoords = GetEntityCoords(playerped)
local handle, ped = FindFirstVehicle()
local success
local rped = nil
local distanceFrom
repeat
local pos = GetEntityCoords(ped)
local distance = GetDistanceBetweenCoords(playerCoords, pos, true)
if canPedBeUsed(ped) and distance < 30.0 and (distanceFrom == nil or distance < distanceFrom) then
distanceFrom = distance
rped = ped
-- FreezeEntityPosition(ped, inFreeze)
if IsEntityTouchingEntity(GetPlayerPed(-1), ped) then
DrawText3Ds(pos["x"],pos["y"],pos["z"]+1, "Veh: " .. ped .. " Model: " .. GetEntityModel(ped) .. " - RØRER" )
else
DrawText3Ds(pos["x"],pos["y"],pos["z"]+1, "Veh: " .. ped .. " Model: " .. GetEntityModel(ped) .. "" )
end
if lowGrav then
SetEntityCoords(ped,pos["x"],pos["y"],pos["z"]+5.0)
end
end
success, ped = FindNextVehicle(handle)
until not success
EndFindVehicle(handle)
return rped
end
function GetObject()
local playerped = GetPlayerPed(-1)
local playerCoords = GetEntityCoords(playerped)
local handle, ped = FindFirstObject()
local success
local rped = nil
local distanceFrom
repeat
local pos = GetEntityCoords(ped)
local distance = GetDistanceBetweenCoords(playerCoords, pos, true)
if distance < 5.0 then
distanceFrom = distance
rped = ped
--FreezeEntityPosition(ped, inFreeze)
if IsEntityTouchingEntity(GetPlayerPed(-1), ped) then
DrawText3Ds(pos["x"],pos["y"],pos["z"]+1, "Obj: " .. ped .. " Model: " .. GetEntityModel(ped) .. " - RØRER" )
-- DrawText3Ds(pos["x"],pos["y"],pos["z"]+0.8, "Coordinates: "..pos["x"]..", "..pos["y"]..", "..pos["z"].." IN CONTACT" )
else
DrawText3Ds(pos["x"],pos["y"],pos["z"]+1, "Obj: " .. ped .. " Model: " .. GetEntityModel(ped) .. "" )
-- DrawText3Ds(pos["x"],pos["y"],pos["z"]+0.8, "Coordinates: "..pos["x"]..", "..pos["y"]..", "..pos["z"].."")
end
if lowGrav then
--ActivatePhysics(ped)
SetEntityCoords(ped,pos["x"],pos["y"],pos["z"]+0.1)
FreezeEntityPosition(ped, false)
end
end
success, ped = FindNextObject(handle)
until not success
EndFindObject(handle)
return rped
end
function getNPC()
local playerped = GetPlayerPed(-1)
local playerCoords = GetEntityCoords(playerped)
local handle, ped = FindFirstPed()
local success
local rped = nil
local distanceFrom
repeat
local pos = GetEntityCoords(ped)
local distance = GetDistanceBetweenCoords(playerCoords, pos, true)
if canPedBeUsed(ped) and distance < 30.0 and (distanceFrom == nil or distance < distanceFrom) then
distanceFrom = distance
rped = ped
if IsEntityTouchingEntity(GetPlayerPed(-1), ped) then
DrawText3Ds(pos["x"],pos["y"],pos["z"], "Ped: " .. ped .. " Model: " .. GetEntityModel(ped) .. " Relationship HASH: " .. GetPedRelationshipGroupHash(ped) .. " IN CONTACT" )
else
DrawText3Ds(pos["x"],pos["y"],pos["z"], "Ped: " .. ped .. " Model: " .. GetEntityModel(ped) .. " Relationship HASH: " .. GetPedRelationshipGroupHash(ped) )
end
FreezeEntityPosition(ped, inFreeze)
if lowGrav then
SetPedToRagdoll(ped, 511, 511, 0, 0, 0, 0)
SetEntityCoords(ped,pos["x"],pos["y"],pos["z"]+0.1)
end
end
success, ped = FindNextPed(handle)
until not success
EndFindPed(handle)
return rped
end
function canPedBeUsed(ped)
if ped == nil then
return false
end
if ped == GetPlayerPed(-1) then
return false
end
if not DoesEntityExist(ped) then
return false
end
return true
end
Citizen.CreateThread( function()
while true do
Citizen.Wait(1)
if dickheaddebug then
local pos = GetEntityCoords(GetPlayerPed(-1))
local forPos = GetOffsetFromEntityInWorldCoords(GetPlayerPed(-1), 0, 1.0, 0.0)
local backPos = GetOffsetFromEntityInWorldCoords(GetPlayerPed(-1), 0, -1.0, 0.0)
local LPos = GetOffsetFromEntityInWorldCoords(GetPlayerPed(-1), 1.0, 0.0, 0.0)
local RPos = GetOffsetFromEntityInWorldCoords(GetPlayerPed(-1), -1.0, 0.0, 0.0)
local forPos2 = GetOffsetFromEntityInWorldCoords(GetPlayerPed(-1), 0, 2.0, 0.0)
local backPos2 = GetOffsetFromEntityInWorldCoords(GetPlayerPed(-1), 0, -2.0, 0.0)
local LPos2 = GetOffsetFromEntityInWorldCoords(GetPlayerPed(-1), 2.0, 0.0, 0.0)
local RPos2 = GetOffsetFromEntityInWorldCoords(GetPlayerPed(-1), -2.0, 0.0, 0.0)
local x, y, z = table.unpack(GetEntityCoords(GetPlayerPed(-1), true))
local currentStreetHash, intersectStreetHash = GetStreetNameAtCoord(x, y, z, currentStreetHash, intersectStreetHash)
currentStreetName = GetStreetNameFromHashKey(currentStreetHash)
drawTxt(0.8, 0.50, 0.4,0.4,0.30, "Heading: " .. GetEntityHeading(GetPlayerPed(-1)), 55, 155, 55, 255)
drawTxt(0.8, 0.52, 0.4,0.4,0.30, "Coords: " .. pos, 55, 155, 55, 255)
drawTxt(0.8, 0.54, 0.4,0.4,0.30, "Attached Ent: " .. GetEntityAttachedTo(GetPlayerPed(-1)), 55, 155, 55, 255)
drawTxt(0.8, 0.56, 0.4,0.4,0.30, "Health: " .. GetEntityHealth(GetPlayerPed(-1)), 55, 155, 55, 255)
drawTxt(0.8, 0.58, 0.4,0.4,0.30, "H a G: " .. GetEntityHeightAboveGround(GetPlayerPed(-1)), 55, 155, 55, 255)
drawTxt(0.8, 0.60, 0.4,0.4,0.30, "Model: " .. GetEntityModel(GetPlayerPed(-1)), 55, 155, 55, 255)
drawTxt(0.8, 0.62, 0.4,0.4,0.30, "Speed: " .. GetEntitySpeed(GetPlayerPed(-1)), 55, 155, 55, 255)
drawTxt(0.8, 0.64, 0.4,0.4,0.30, "Frame Time: " .. GetFrameTime(), 55, 155, 55, 255)
drawTxt(0.8, 0.66, 0.4,0.4,0.30, "Street: " .. currentStreetName, 55, 155, 55, 255)
DrawLine(pos,forPos, 255,0,0,115)
DrawLine(pos,backPos, 255,0,0,115)
DrawLine(pos,LPos, 255,255,0,115)
DrawLine(pos,RPos, 255,255,0,115)
DrawLine(forPos,forPos2, 255,0,255,115)
DrawLine(backPos,backPos2, 255,0,255,115)
DrawLine(LPos,LPos2, 255,255,255,115)
DrawLine(RPos,RPos2, 255,255,255,115)
local nearped = getNPC()
local veh = GetVehicle()
local nearobj = GetObject()
else
Citizen.Wait(5000)
end
end
end)

View File

@ -0,0 +1,40 @@
-- Open Inventory
RegisterNetEvent('ps-adminmenu:client:openInventory', function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local player = selectedData["Player"].value
if Config.Inventory == 'ox_inventory' then
TriggerServerEvent("ps-adminmenu:server:OpenInv", player)
else
TriggerServerEvent("inventory:server:OpenInventory", "otherplayer", player)
end
end)
-- Open Stash
RegisterNetEvent('ps-adminmenu:client:openStash', function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local stash = selectedData["Stash"].value
if Config.Inventory == 'ox_inventory' then
TriggerServerEvent("ps-adminmenu:server:OpenStash", stash)
else
TriggerServerEvent("inventory:server:OpenInventory", "stash", tostring(stash))
TriggerEvent("inventory:client:SetCurrentStash", tostring(stash))
end
end)
-- Open Trunk
RegisterNetEvent('ps-adminmenu:client:openTrunk', function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local vehiclePlate = selectedData["Plate"].value
if Config.Inventory == 'ox_inventory' then
TriggerServerEvent("ps-adminmenu:server:OpenTrunk", vehiclePlate)
else
TriggerServerEvent("inventory:server:OpenInventory", "trunk", tostring(vehiclePlate))
TriggerEvent("inventory:client:SetCurrentStash", tostring(vehiclePlate))
end
end)

View File

@ -0,0 +1,79 @@
QBCore = exports['qb-core']:GetCoreObject()
PlayerData = {}
-- Functions
local function setupMenu()
Wait(500)
PlayerData = QBCore.Functions.GetPlayerData()
local resources = lib.callback.await('ps-adminmenu:callback:GetResources', false)
local commands = lib.callback.await('ps-adminmenu:callback:GetCommands', false)
GetData()
SendNUIMessage({
action = "setupUI",
data = {
actions = Config.Actions,
resources = resources,
playerData = PlayerData,
commands = commands
}
})
end
-- Event Handlers
AddEventHandler("QBCore:Client:OnPlayerLoaded", function()
setupMenu()
end)
AddEventHandler("onResourceStart", function(resourceName)
if (GetCurrentResourceName() == resourceName) then
setupMenu()
end
end)
-- NUICallbacks
RegisterNUICallback("hideUI", function()
ToggleUI(false)
end)
RegisterNUICallback("clickButton", function(data)
local selectedData = data.selectedData
local key = data.data
local data = CheckDataFromKey(key)
if not data or not CheckPerms(data.perms) then return end
if data.type == "client" then
TriggerEvent(data.event, key, selectedData)
elseif data.type == "server" then
TriggerServerEvent(data.event, key, selectedData)
elseif data.type == "command" then
ExecuteCommand(data.event)
end
Log("Action Used",
PlayerData.name ..
" (" ..
PlayerData.citizenid ..
") - Used: " .. data.label .. (selectedData and (" with args: " .. json.encode(selectedData)) or ""))
end)
-- Open UI Event
RegisterNetEvent('ps-adminmenu:client:OpenUI', function()
ToggleUI(true)
end)
-- Close UI Event
RegisterNetEvent('ps-adminmenu:client:CloseUI', function()
ToggleUI(false)
end)
-- Change resource state
RegisterNUICallback("setResourceState", function(data, cb)
local resources = lib.callback.await('ps-adminmenu:callback:ChangeResourceState', false, data)
cb(resources)
end)
-- Get players
RegisterNUICallback("getPlayers", function(data, cb)
local players = lib.callback.await('ps-adminmenu:callback:GetPlayers', false)
cb(players)
end)

View File

@ -0,0 +1,221 @@
-- Toggles Invincibility
local visible = true
RegisterNetEvent('ps-adminmenu:client:ToggleInvisible', function(data)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
visible = not visible
SetEntityVisible(cache.ped, visible, 0)
end)
-- God Mode
local godmode = false
RegisterNetEvent('ps-adminmenu:client:ToggleGodmode', function(data)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
godmode = not godmode
if godmode then
QBCore.Functions.Notify(locale("godmode", "enabled"), 'primary')
exports['qb-ambulancejob']:ResetAll()
while godmode do
Wait(0)
SetPlayerInvincible(cache.playerId, true)
end
SetPlayerInvincible(cache.playerId, false)
QBCore.Functions.Notify(locale("godmode", "disabled"), 'primary')
end
end)
-- Cuff/Uncuff
RegisterNetEvent('ps-adminmenu:client:ToggleCuffs', function(player)
local target = GetPlayerServerId(player)
TriggerEvent("police:client:GetCuffed", target)
end)
-- Copy Coordinates
local function CopyCoords(data)
local coords = GetEntityCoords(cache.ped)
local heading = GetEntityHeading(cache.ped)
local formats = { vector2 = "%.2f, %.2f", vector3 = "%.2f, %.2f, %.2f", vector4 = "%.2f, %.2f, %.2f, %.2f", heading =
"%.2f" }
local format = formats[data]
local clipboardText = ""
if data == "vector2" then
clipboardText = string.format(format, coords.x, coords.y)
elseif data == "vector3" then
clipboardText = string.format(format, coords.x, coords.y, coords.z)
elseif data == "vector4" then
clipboardText = string.format(format, coords.x, coords.y, coords.z, heading)
elseif data == "heading" then
clipboardText = string.format(format, heading)
end
lib.setClipboard(clipboardText)
end
RegisterCommand("vector2", function()
if not CheckPerms('mod') then return end
CopyCoords("vector2")
end, false)
RegisterCommand("vector3", function()
if not CheckPerms('mod') then return end
CopyCoords("vector3")
end, false)
RegisterCommand("vector4", function()
if not CheckPerms('mod') then return end
CopyCoords("vector4")
end, false)
RegisterCommand("heading", function()
if not CheckPerms('mod') then return end
CopyCoords("heading")
end, false)
-- Infinite Ammo
local InfiniteAmmo = false
RegisterNetEvent('ps-adminmenu:client:setInfiniteAmmo', function(data)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
InfiniteAmmo = not InfiniteAmmo
if GetAmmoInPedWeapon(cache.ped, cache.weapon) < 6 then
SetAmmoInClip(cache.ped, cache.weapon, 10)
Wait(50)
end
while InfiniteAmmo do
SetPedInfiniteAmmo(cache.ped, true, cache.weapon)
RefillAmmoInstantly(cache.ped)
Wait(250)
end
SetPedInfiniteAmmo(cache.ped, false, cache.weapon)
end)
-- Toggle coords
local showCoords = false
local function showCoordsMenu()
while showCoords do
Wait(50)
local coords = GetEntityCoords(PlayerPedId())
local heading = GetEntityHeading(PlayerPedId())
SendNUIMessage({
action = "showCoordsMenu",
data = {
show = showCoords,
x = QBCore.Shared.Round(coords.x, 2),
y = QBCore.Shared.Round(coords.y, 2),
z = QBCore.Shared.Round(coords.z, 2),
heading = QBCore.Shared.Round(heading, 2)
}
})
end
end
RegisterNetEvent('ps-adminmenu:client:ToggleCoords', function(data)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
showCoords = not showCoords
if showCoords then
CreateThread(showCoordsMenu)
end
end)
-- Set Ammo
RegisterNetEvent('ps-adminmenu:client:SetAmmo', function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local ammo = selectedData["Ammo Ammount"].value
local weapon = GetSelectedPedWeapon(cache.ped)
if weapon ~= nil then
SetPedAmmo(cache.ped, weapon, ammo)
QBCore.Functions.Notify(locale("set_wepaon_ammo", tostring(ammo)), 'success')
else
QBCore.Functions.Notify(locale("no_weapon"), 'error')
end
end)
RegisterCommand("setammo", function(source)
if not CheckPerms('mod') then return end
local weapon = GetSelectedPedWeapon(cache.ped)
local ammo = 999
if weapon ~= nil then
SetPedAmmo(cache.ped, weapon, ammo)
QBCore.Functions.Notify(locale("set_wepaon_ammo", tostring(ammo)), 'success')
else
QBCore.Functions.Notify(locale("no_weapon"), 'error')
end
end, false)
--Toggle Dev
local ToggleDev = false
RegisterNetEvent('ps-adminmenu:client:ToggleDev', function(dataKey)
local data = CheckDataFromKey(dataKey)
if not data or not CheckPerms(data.perms) then return end
ToggleDev = not ToggleDev
SetPlayerInvincible(PlayerId(), ToggleDev) -- toggle dev mode (ps-hud/qb-hud)
TriggerEvent('ps-adminmenu:client:ToggleCoords', dataKey) -- toggle Coords
TriggerEvent('ps-adminmenu:client:ToggleGodmode', dataKey) -- Godmode
QBCore.Functions.Notify(locale("toggle_dev"), 'success')
end)
-- Key Bindings
local toogleAdmin = lib.addKeybind({
name = 'toogleAdmin',
description = locale("command_admin_desc"),
defaultKey = Config.AdminKey,
onPressed = function(self)
ExecuteCommand('admin')
end
})
--noclip
RegisterCommand('nc', function()
TriggerEvent(Config.Actions["noclip"].event)
end, false)
RegisterCommand('debug', function()
TriggerEvent(Config.Actions["debug"].event)
end, false)
RegisterCommand('dev', function()
TriggerEvent(Config.OtherActions["toggleDevmode"].event)
TriggerEvent(Config.Actions["debug"].event)
end, false)
local toogleNoclip = lib.addKeybind({
name = 'toogleNoclip',
description = locale("command_noclip_desc"),
defaultKey = Config.NoclipKey,
onPressed = function(self)
ExecuteCommand('nc')
end
})
if Config.Keybindings then
toogleAdmin:disable(false)
toogleNoclip:disable(false)
else
toogleAdmin:disable(true)
toogleNoclip:disable(true)
end
-- Set Ped
RegisterNetEvent("ps-adminmenu:client:setPed", function(pedModels)
lib.requestModel(pedModels, 1500)
SetPlayerModel(cache.playerId, pedModels)
SetPedDefaultComponentVariation(cache.ped)
SetModelAsNoLongerNeeded(pedModels)
end)

View File

@ -0,0 +1,206 @@
local noclip = false
local cam = 0
local ped
local speed = 1
local maxSpeed = 16
-- Disable the controls
local function DisabledControls()
HudWeaponWheelIgnoreSelection()
DisableAllControlActions(0)
DisableAllControlActions(1)
DisableAllControlActions(2)
EnableControlAction(0, 220, true)
EnableControlAction(0, 221, true)
EnableControlAction(0, 245, true)
end
-- Setup the camera
local function SetupCam()
local rotation = GetEntityRotation(ped)
local coords = GetEntityCoords(ped)
cam = CreateCameraWithParams("DEFAULT_SCRIPTED_CAMERA", coords, vector3(0.0, 0.0, rotation.z), 75.0)
SetCamActive(cam, true)
RenderScriptCams(true, true, 1000, false, false)
AttachCamToEntity(cam, ped, 0.0, 0.0, 1.0, true)
end
-- Destroys the camera
local function DestoryCam()
Wait(100)
SetGameplayCamRelativeHeading(0)
RenderScriptCams(false, true, 1000, true, true)
DetachEntity(ped, true, true)
SetCamActive(cam, false)
DestroyCam(cam, true)
end
-- Checks if a control is always pressed
local IsControlAlwaysPressed = function(inputGroup, control)
return IsControlPressed(inputGroup, control) or IsDisabledControlPressed(inputGroup, control)
end
-- Updates the camera rotation
local function UpdateCameraRotation()
local rightAxisX = GetControlNormal(0, 220)
local rightAxisY = GetControlNormal(0, 221)
local rotation = GetCamRot(cam, 2)
local yValue = rightAxisY * -5
local newX
local newZ = rotation.z + (rightAxisX * -10)
if (rotation.x + yValue > -89.0) and (rotation.x + yValue < 89.0) then
newX = rotation.x + yValue
end
if newX ~= nil and newZ ~= nil then
SetCamRot(cam, vector3(newX, rotation.y, newZ), 2)
end
SetEntityHeading(ped, math.max(0, (rotation.z % 360)))
end
-- Gets the ground coords
local function TeleportToGround()
local coords = GetEntityCoords(ped)
local rayCast = StartShapeTestRay(coords.x, coords.y, coords.z, coords.x, coords.y, -10000.0, 1, 0)
local _, hit, hitCoords = GetShapeTestResult(rayCast)
if hit == 1 then
SetEntityCoords(ped, hitCoords.x, hitCoords.y, hitCoords.z)
else
SetEntityCoords(ped, coords.x, coords.y, coords.z)
end
end
-- Toggles the behavior of visiblty, collision, etc
local function ToggleBehavior(bool)
local coords = GetEntityCoords(ped)
RequestCollisionAtCoord(coords.x, coords.y, coords.z)
FreezeEntityPosition(ped, bool)
SetEntityCollision(ped, not bool, not bool)
SetEntityVisible(ped, not bool, not bool)
SetEntityInvincible(ped, bool)
SetEntityAlpha(ped, bool and noclipAlpha or 255, false)
SetLocalPlayerVisibleLocally(true)
SetEveryoneIgnorePlayer(ped, bool)
SetPoliceIgnorePlayer(ped, bool)
local vehicle = GetVehiclePedIsIn(ped, false)
if vehicle ~= 0 then
SetEntityAlpha(vehicle, bool and noclipAlpha or 255, false)
end
end
-- Stops the noclip
local function StopNoclip()
DestoryCam()
TeleportToGround()
ToggleBehavior(false)
end
-- Handels the speed
local function UpdateSpeed()
if IsControlAlwaysPressed(2, 14) then
speed = speed - 0.5
if speed < 0.5 then
speed = 0.5
end
elseif IsControlAlwaysPressed(2, 15) then
speed = speed + 0.5
if speed > maxSpeed then
speed = maxSpeed
end
elseif IsDisabledControlJustReleased(0, 348) then
speed = 1
end
end
-- Handels the movement
local function UpdateMovement()
local multi = 1.0
if IsControlAlwaysPressed(0, 21) then
multi = 2
elseif IsControlAlwaysPressed(0, 19) then
multi = 4
elseif IsControlAlwaysPressed(0, 36) then
multi = 0.25
end
if IsControlAlwaysPressed(0, 32) then
local pitch = GetCamRot(cam, 0)
if pitch.x >= 0 then
SetEntityCoordsNoOffset(ped,
GetOffsetFromEntityInWorldCoords(ped, 0.0, 0.5 * (speed * multi),
(pitch.x * ((speed / 2) * multi)) / 89))
else
SetEntityCoordsNoOffset(ped,
GetOffsetFromEntityInWorldCoords(ped, 0.0, 0.5 * (speed * multi),
-1 * ((math.abs(pitch.x) * ((speed / 2) * multi)) / 89)))
end
elseif IsControlAlwaysPressed(0, 33) then
local pitch = GetCamRot(cam, 2)
if pitch.x >= 0 then
SetEntityCoordsNoOffset(ped,
GetOffsetFromEntityInWorldCoords(ped, 0.0, -0.5 * (speed * multi),
-1 * (pitch.x * ((speed / 2) * multi)) / 89))
else
SetEntityCoordsNoOffset(ped,
GetOffsetFromEntityInWorldCoords(ped, 0.0, -0.5 * (speed * multi),
((math.abs(pitch.x) * ((speed / 2) * multi)) / 89)))
end
end
if IsControlAlwaysPressed(0, 34) then
SetEntityCoordsNoOffset(ped,
GetOffsetFromEntityInWorldCoords(ped, -0.5 * (speed * multi), 0.0, 0.0))
elseif IsControlAlwaysPressed(0, 35) then
SetEntityCoordsNoOffset(ped,
GetOffsetFromEntityInWorldCoords(ped, 0.5 * (speed * multi), 0.0, 0.0))
end
if IsControlAlwaysPressed(0, 44) then
SetEntityCoordsNoOffset(ped,
GetOffsetFromEntityInWorldCoords(ped, 0.0, 0.0, 0.5 * (speed * multi)))
elseif IsControlAlwaysPressed(0, 46) then
SetEntityCoordsNoOffset(ped,
GetOffsetFromEntityInWorldCoords(ped, 0.0, 0.0, -0.5 * (speed * multi)))
end
end
-- Toggles the noclip
local function ToggleNoclip()
noclip = not noclip
if cache.vehicle then
ped = cache.vehicle
else
ped = cache.ped
end
if noclip then
SetupCam()
ToggleBehavior(true)
while noclip do
Wait(0)
UpdateCameraRotation()
DisabledControls()
UpdateSpeed()
UpdateMovement()
end
else
StopNoclip()
end
end
RegisterNetEvent('ps-adminmenu:client:ToggleNoClip', function()
if not CheckPerms(Config.Actions["noclip"].perms) then return end
ToggleNoclip()
end)

View File

@ -0,0 +1,182 @@
local ShowBlips = false
local ShowNames = false
local NetCheck1 = false
local NetCheck2 = false
local Blip = nil
local Tag = nil
local currentPlayers = {}
-- Function to remove all names and Blips
local function removeNameAndBlips()
if DoesBlipExist(Blip) then
RemoveBlip(Blip)
end
if Tag then
SetMpGamerTagVisibility(Tag, 0, false)
SetMpGamerTagVisibility(Tag, 2, false)
SetMpGamerTagVisibility(Tag, 4, false)
SetMpGamerTagVisibility(Tag, 6, false)
RemoveMpGamerTag(Tag)
end
end
-- Function to Toggle Blips and Names
local function ToggleBlipsAndNames(isBlips)
if isBlips then
ShowBlips = not ShowBlips
NetCheck1 = ShowBlips
local message = ShowBlips and "blips_activated" or "blips_deactivated"
QBCore.Functions.Notify(locale(message), ShowBlips and "success" or "error")
else
ShowNames = not ShowNames
NetCheck2 = ShowNames
local message = ShowNames and "names_activated" or "names_deactivated"
QBCore.Functions.Notify(locale(message), ShowNames and "success" or "error")
end
if not ShowNames or not ShowBlips then
removeNameAndBlips()
end
end
-- Main Function to Update Blips and Names
local function UpdateBlipsAndNames(players)
local playerPed = PlayerPedId()
local playerCoords = GetEntityCoords(playerPed, true)
local blipSprites = { -- Sprite Per Vehicle Class
[1] = 1,
[8] = 226,
[9] = 757,
[10] = 477,
[11] = 477,
[12] = 67,
[13] = 226,
[14] = 427,
[15] = 422,
[16] = 423,
[17] = 198,
[18] = 56,
[19] = 421,
[20] = 477,
}
for _, player in pairs(players) do
local playerId = GetPlayerFromServerId(player.id)
local ped = GetPlayerPed(playerId)
local name = 'ID: ' .. player.id .. ' | ' .. player.name
Blip = GetBlipFromEntity(ped)
Tag = CreateFakeMpGamerTag(ped, name, false, false, "", false)
SetMpGamerTagAlpha(Tag, 0, 255)
SetMpGamerTagAlpha(Tag, 2, 255)
SetMpGamerTagAlpha(Tag, 4, 255)
SetMpGamerTagAlpha(Tag, 6, 255)
SetMpGamerTagHealthBarColour(Tag, 25)
local isPlayerTalking = NetworkIsPlayerTalking(playerId)
local isPlayerInvincible = GetPlayerInvincible(playerId)
if ShowNames then
SetMpGamerTagVisibility(Tag, 0, true)
SetMpGamerTagVisibility(Tag, 2, true)
SetMpGamerTagVisibility(Tag, 4, isPlayerTalking)
SetMpGamerTagVisibility(Tag, 6, isPlayerInvincible)
else
SetMpGamerTagVisibility(Tag, 0, false)
SetMpGamerTagVisibility(Tag, 2, false)
SetMpGamerTagVisibility(Tag, 4, false)
SetMpGamerTagVisibility(Tag, 6, false)
RemoveMpGamerTag(Tag)
end
if ShowBlips then
if not DoesBlipExist(Blip) then
Blip = AddBlipForEntity(ped)
ShowHeadingIndicatorOnBlip(Blip, true)
SetBlipCategory(Blip, 7)
else
local veh = GetVehiclePedIsIn(ped, false)
local classveh = GetVehicleClass(veh)
local modelveh = GetEntityModel(veh)
if veh ~= 0 then
local blipSprite = blipSprites[classveh] or 225
if modelveh == 'besra' or modelveh == 'hydra' or modelveh == 'lazer' then
blipSprite = 424
end
SetBlipSprite(Blip, blipSprite)
ShowHeadingIndicatorOnBlip(Blip, false)
local passengers = GetVehicleNumberOfPassengers(veh)
if passengers then
if not IsVehicleSeatFree(veh, -1) then
passengers = passengers + 1
end
ShowNumberOnBlip(Blip, passengers)
else
HideNumberOnBlip(Blip)
end
SetBlipRotation(Blip, math.ceil(GetEntityHeading(veh)))
SetBlipNameToPlayerName(Blip, playerId)
SetBlipScale(Blip, 0.85)
local distance = math.floor(Vdist(playerCoords.x, playerCoords.y, playerCoords.z,
GetEntityCoords(ped, true).x, GetEntityCoords(ped, true).y, GetEntityCoords(ped, true).z) /
-1) +
900
distance = math.max(0, math.min(255, distance))
SetBlipAlpha(Blip, distance)
else
HideNumberOnBlip(Blip)
SetBlipSprite(Blip, 1)
SetBlipNameToPlayerName(Blip, playerId)
ShowHeadingIndicatorOnBlip(Blip, true)
end
end
end
end
end
local function preparePlayers()
currentPlayers = {}
Wait(100)
currentPlayers = lib.callback.await('ps-adminmenu:callback:GetPlayers')
end
-- Toggle Blips and Names events
RegisterNetEvent('ps-adminmenu:client:toggleBlips', function(data)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
if not ShowBlips then preparePlayers() end
ToggleBlipsAndNames(true)
end)
RegisterNetEvent('ps-adminmenu:client:toggleNames', function(data)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
if not ShowNames then preparePlayers() end
ToggleBlipsAndNames(false)
end)
-- Mute Player
RegisterNetEvent("ps-adminmenu:client:MutePlayer", function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local playerId = selectedData["Player"].value
if not playerId then return end
exports["pma-voice"]:toggleMutePlayer(playerId)
end)
-- Main loop to check for updates
CreateThread(function()
while true do
Wait(1000)
if NetCheck1 or NetCheck2 then
UpdateBlipsAndNames(currentPlayers)
end
end
end)
-- Remove Stress
RegisterNetEvent('ps-adminmenu:client:removeStress', function(data)
TriggerServerEvent('hud:server:RelieveStress', 100)
end)

View File

@ -0,0 +1,47 @@
local oldPos = nil
local spectateInfo = {
toggled = false,
target = 0,
targetPed = 0
}
RegisterNetEvent('ps-adminmenu:requestSpectate', function(targetPed, target, name)
oldPos = GetEntityCoords(cache.ped)
spectateInfo = {
toggled = true,
target = target,
targetPed = targetPed
}
end)
RegisterNetEvent('ps-adminmenu:cancelSpectate', function()
if NetworkIsInSpectatorMode() then
NetworkSetInSpectatorMode(false, spectateInfo['targetPed'])
end
SetEntityVisible(cache.ped, true, 0)
spectateInfo = { toggled = false, target = 0, targetPed = 0 }
RequestCollisionAtCoord(oldPos)
SetEntityCoords(cache.ped, oldPos)
oldPos = nil;
end)
CreateThread(function()
while true do
Wait(0)
if spectateInfo['toggled'] then
local targetPed = NetworkGetEntityFromNetworkId(spectateInfo.targetPed)
if DoesEntityExist(targetPed) then
SetEntityVisible(cache.ped, false, 0)
if not NetworkIsInSpectatorMode() then
RequestCollisionAtCoord(GetEntityCoords(targetPed))
NetworkSetInSpectatorMode(true, targetPed)
end
else
TriggerServerEvent('ps-adminmenu:spectate:teleport', spectateInfo['target'])
while not DoesEntityExist(NetworkGetEntityFromNetworkId(spectateInfo.targetPed)) do Wait(100) end
end
else
Wait(500)
end
end
end)

View File

@ -0,0 +1,65 @@
local lastCoords
local function teleport(x, y, z)
if cache.vehicle then
return SetPedCoordsKeepVehicle(cache.ped, x, y, z)
end
SetEntityCoords(cache.ped, x, y, z, false, false, false, false)
end
-- Teleport to player
RegisterNetEvent('ps-adminmenu:client:TeleportToPlayer', function(coords)
lastCoords = GetEntityCoords(cache.ped)
SetPedCoordsKeepVehicle(cache.ped, coords.x, coords.y, coords.z)
end)
-- Teleport to coords
RegisterNetEvent('ps-adminmenu:client:TeleportToCoords', function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local coordsStr = selectedData["Coords"].value
local x, y, z, heading
x, y, z, heading = coordsStr:match("(-?%d+%.?%d*),%s*(-?%d+%.?%d*),?%s*(-?%d*%.?%d*),?%s*(-?%d*%.?%d*)")
if not x or not y then
x, y, z, heading = coordsStr:match("(-?%d+%.?%d*)%s+(-?%d+%.?%d*)%s*(-?%d*%.?%d*)%s*(-?%d*%.?%d*)")
end
x = tonumber(x)
y = tonumber(y)
z = tonumber(z or 0)
heading = tonumber(heading or 0)
if x and y then
lastCoords = GetEntityCoords(cache.ped)
if heading and heading ~= 0 then
SetEntityHeading(cache.ped, heading)
end
SetPedCoordsKeepVehicle(cache.ped, x, y, z)
end
end)
-- Teleport to Locaton
RegisterNetEvent('ps-adminmenu:client:TeleportToLocation', function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local coords = selectedData["Location"].value
lastCoords = GetEntityCoords(cache.ped)
SetPedCoordsKeepVehicle(cache.ped, coords.x, coords.y, coords.z)
end)
-- Teleport back
RegisterNetEvent('ps-adminmenu:client:TeleportBack', function(data)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
if lastCoords then
local coords = GetEntityCoords(cache.ped)
teleport(lastCoords.x, lastCoords.y, lastCoords.z)
lastCoords = coords
end
end)

View File

@ -0,0 +1,134 @@
local ObjectList = require "data.object"
local function DrawEntityBoundingBox(entity, color)
local model = GetEntityModel(entity)
local min, max = GetModelDimensions(model)
local rightVector, forwardVector, upVector, position = GetEntityMatrix(entity)
-- Calculate size
local dim =
{
x = 0.5 * (max.x - min.x),
y = 0.5 * (max.y - min.y),
z = 0.5 * (max.z - min.z)
}
-- Calculate the eight bounding box edges
local edges = {}
edges[1] = position - dim.y * rightVector - dim.x * forwardVector - dim.z * upVector
edges[2] = edges[1] + 2 * dim.y * rightVector
edges[3] = edges[2] + 2 * dim.z * upVector
edges[4] = edges[1] + 2 * dim.z * upVector
edges[5] = position + dim.y * rightVector + dim.x * forwardVector + dim.z * upVector
edges[6] = edges[5] - 2 * dim.y * rightVector
edges[7] = edges[6] - 2 * dim.z * upVector
edges[8] = edges[5] - 2 * dim.z * upVector
-- Draw lines to connect the edges and create the bounding box
for i = 1, 4 do
local j = i % 4 + 1
DrawLine(edges[i].x, edges[i].y, edges[i].z, edges[j].x, edges[j].y, edges[j].z, color.r, color.g, color.b, color.a)
DrawLine(edges[i + 4].x, edges[i + 4].y, edges[i + 4].z, edges[j + 4].x, edges[j + 4].y, edges[j + 4].z, color.r, color.g, color.b, color.a)
DrawLine(edges[i].x, edges[i].y, edges[i].z, edges[i + 4].x, edges[i + 4].y, edges[i + 4].z, color.r, color.g, color.b, color.a)
end
end
local function RotationToDirection(rotation)
local adjustedRotation =
{
x = (math.pi / 180) * rotation.x,
y = (math.pi / 180) * rotation.y,
z = (math.pi / 180) * rotation.z
}
local direction =
{
x = -math.sin(adjustedRotation.z) * math.abs(math.cos(adjustedRotation.x)),
y = math.cos(adjustedRotation.z) * math.abs(math.cos(adjustedRotation.x)),
z = math.sin(adjustedRotation.x)
}
return direction
end
local function RayCastGamePlayCamera(distance)
local cameraRotation = GetGameplayCamRot()
local cameraCoord = GetGameplayCamCoord()
local direction = RotationToDirection(cameraRotation)
local destination =
{
x = cameraCoord.x + direction.x * distance,
y = cameraCoord.y + direction.y * distance,
z = cameraCoord.z + direction.z * distance
}
local a, b, c, d, e = GetShapeTestResult(StartShapeTestRay(cameraCoord.x, cameraCoord.y, cameraCoord.z, destination.x, destination.y, destination.z, -1, PlayerPedId(), 0))
return b, c, e
end
-- Toggle Delete Laser
local activeLaser = false
RegisterNetEvent('ps-adminmenu:client:ToggleLaser', function()
local x = 0.4
local y = 0.025
activeLaser = not activeLaser
CreateThread(function()
while true do
local wait = 7
if activeLaser then
local color = {r = 255, g = 255, b = 255, a = 200}
local position = GetEntityCoords(PlayerPedId())
local hit, coords, entity = RayCastGamePlayCamera(1000.0)
local objectData = {}
DisableControlAction(0, 200)
DisableControlAction(0, 26)
if hit and (IsEntityAVehicle(entity) or IsEntityAPed(entity) or IsEntityAnObject(entity)) then
local entityCoord = GetEntityCoords(entity)
local heading = GetEntityHeading(entity)
local model = GetEntityModel(entity)
local minimum, maximum = GetModelDimensions(model)
DrawEntityBoundingBox(entity, color)
DrawLine(position.x, position.y, position.z, coords.x, coords.y, coords.z, color.r, color.g, color.b, color.a)
objectData.hash = model
objectData.name = ObjectList[model]
objectData.coords = ("vec4(%s, %s, %s, %s)"):format(entityCoord.x, entityCoord.y, entityCoord.z, heading)
if IsControlJustReleased(0, 38) then
SetEntityAsMissionEntity(entity, true, true)
DeleteEntity(entity)
end
if IsDisabledControlJustReleased(0, 26) then
lib.setClipboard(json.encode(objectData, {indent = true}))
end
elseif coords.x ~= 0.0 and coords.y ~= 0.0 then
DrawLine(position.x, position.y, position.z, coords.x, coords.y, coords.z, color.r, color.g, color.b, color.a)
DrawMarker(28, coords.x, coords.y, coords.z, 0.0, 0.0, 0.0, 0.0, 180.0, 0.0, 0.1, 0.1, 0.1, color.r, color.g, color.b, color.a, false, true, 2, nil, nil, false)
end
if IsDisabledControlJustReleased(0, 200) then
activeLaser = not activeLaser
end
SendNUIMessage({
action = "showEntityInfo",
data = {
show = true,
hash = objectData.hash or "",
name = objectData.name or "",
}
})
else
local wait = 500
SendNUIMessage({
action = "showEntityInfo",
data = {
show = false,
}
})
end
Wait(wait)
end
end)
end)

View File

@ -0,0 +1,48 @@
-- Set on fire
RegisterNetEvent('ps-adminmenu:client:SetOnFire', function(time)
if not time then time = 10 end
local timer = time * 1000
StartEntityFire(cache.serverId)
Wait(timer)
StopEntityFire(cache.serverId)
end)
-- Explode player
RegisterNetEvent('ps-adminmenu:client:ExplodePlayer', function(damage)
local coords = GetEntityCoords(cache.serverId)
if damage == nil then damage = "nodamage" end
if damage == "nodamage" then
AddExplosion(coords.x, coords.y, coords.z, 'EXPLOSION_TANKER', 2.0, true, false, 2.0)
else
AddExplosion(coords.x, coords.y, coords.z, 2, 0.9, 1, 0, 1065353216, 0)
end
end)
-- Play Sound
RegisterNetEvent('ps-adminmenu:client:PlaySound', function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local player = selectedData["Player"].value
local sound = selectedData["Sound"].value
TriggerServerEvent("InteractSound_SV:PlayOnOne", player, sound, 0.30)
end)
-- Drunk Player
RegisterNetEvent('ps-adminmenu:client:InitiateDrunkEffect', function()
local playerPed = cache.ped
lib.requestAnimSet("MOVE_M@DRUNK@VERYDRUNK")
Wait(650)
SetPedMotionBlur(playerPed, true)
SetPedMovementClipset(playerPed, "MOVE_M@DRUNK@VERYDRUNK", true)
SetPedIsDrunk(playerPed, true)
ShakeGameplayCam("DRUNK_SHAKE", 2.0)
Wait(30000) -- Time To Be Drunk
SetPedMoveRateOverride(playerPed, 1.0)
SetRunSprintMultiplierForPlayer(playerPed, 1.0)
SetPedIsDrunk(playerPed, false)
SetPedMotionBlur(playerPed, false)
ResetPedMovementClipset(playerPed)
ShakeGameplayCam("DRUNK_SHAKE", 0.0)
SetTimecycleModifierStrength(0.0)
end)

View File

@ -0,0 +1,53 @@
--- @param bool boolean
function ToggleUI(bool)
SetNuiFocus(bool, bool)
SendNUIMessage({
action = "setVisible",
data = bool
})
end
--- @param perms table
function CheckPerms(perms)
return lib.callback.await('ps-adminmenu:callback:CheckPerms', false, perms)
end
function CheckDataFromKey(key)
local actions = Config.Actions[key]
if actions then
local data = nil
if actions.event then
data = actions
end
if actions.dropdown then
for _, v in pairs(actions.dropdown) do
if v.event then
local new = v
new.perms = actions.perms
data = new
break
end
end
end
return data
end
local playerActions = Config.PlayerActions[key]
if playerActions then
return playerActions
end
local otherActions = Config.OtherActions[key]
if otherActions then
return otherActions
end
end
--- @param title string
--- @param message string
function Log(title, message)
TriggerServerEvent("qb-log:server:CreateLog", "ps-adminmenu", title, "red", message)
end

View File

@ -0,0 +1,208 @@
local function GetVehicleName(hash)
for _, v in pairs(QBCore.Shared.Vehicles) do
if hash == v.hash then
return v.model
end
end
end
-- Own Vehicle
RegisterNetEvent('ps-adminmenu:client:Admincar', function(data)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
if not cache.vehicle then return end
local props = lib.getVehicleProperties(cache.vehicle)
local name = GetVehicleName(props.model)
local sharedVehicles = QBCore.Shared.Vehicles[name]
local hash = GetHashKey(cache.vehicle)
if sharedVehicles then
TriggerServerEvent('ps-adminmenu:server:SaveCar', props, sharedVehicles, hash, props.plate)
else
QBCore.Functions.Notify(locale("cannot_store_veh"), 'error')
end
end)
-- Spawn Vehicle
RegisterNetEvent('ps-adminmenu:client:SpawnVehicle', function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local selectedVehicle = selectedData["Vehicle"].value
local hash = GetHashKey(selectedVehicle)
if not IsModelValid(hash) then return end
lib.requestModel(hash)
if cache.vehicle then
DeleteVehicle(cache.vehicle)
end
local vehicle = CreateVehicle(hash, GetEntityCoords(cache.ped), GetEntityHeading(cache.ped), true, false)
TaskWarpPedIntoVehicle(cache.ped, vehicle, -1)
exports[Config.Fuel]:SetFuel(vehicle, 100.0)
TriggerEvent("vehiclekeys:client:SetOwner", QBCore.Functions.GetPlate(vehicle))
end)
-- Refuel Vehicle
RegisterNetEvent('ps-adminmenu:client:RefuelVehicle', function(data)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
if cache.vehicle then
exports[Config.Fuel]:SetFuel(cache.vehicle, 100.0)
QBCore.Functions.Notify(locale("refueled_vehicle"), 'success')
else
QBCore.Functions.Notify(locale("not_in_vehicle"), 'error')
end
end)
-- Change plate
RegisterNetEvent('ps-adminmenu:client:ChangePlate', function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local plate = selectedData["Plate"].value
if string.len(plate) > 8 then
return QBCore.Functions.Notify(locale("plate_max"), "error", 5000)
end
if cache.vehicle then
local AlreadyPlate = lib.callback.await("ps-adminmenu:callback:CheckAlreadyPlate", false, plate)
if AlreadyPlate then
QBCore.Functions.Notify(locale("already_plate"), "error", 5000)
return
end
local currentPlate = GetVehicleNumberPlateText(cache.vehicle)
TriggerServerEvent('ps-adminmenu:server:ChangePlate', plate, currentPlate)
Wait(100)
SetVehicleNumberPlateText(cache.vehicle, plate)
Wait(100)
TriggerServerEvent('qb-vehiclekeys:server:AcquireVehicleKeys', QBCore.Functions.GetPlate(cache.vehicle))
else
QBCore.Functions.Notify(locale("not_in_vehicle"), 'error')
end
end)
-- Toggle Vehicle Dev mode
local VEHICLE_DEV_MODE = false
local function UpdateVehicleMenu()
while VEHICLE_DEV_MODE do
Wait(1000)
local vehicle = lib.getVehicleProperties(cache.vehicle)
local name = GetVehicleName(vehicle.model)
local netID = VehToNet(cache.vehicle)
SendNUIMessage({
action = "showVehicleMenu",
data = {
show = VEHICLE_DEV_MODE,
name = name,
model = vehicle.model,
netID = netID,
engine_health = vehicle.engineHealth,
body_health = vehicle.bodyHealth,
plate = vehicle.plate,
fuel = vehicle.fuelLevel,
}
})
end
end
RegisterNetEvent('ps-adminmenu:client:ToggleVehDevMenu', function(data)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
if not cache.vehicle then return end
VEHICLE_DEV_MODE = not VEHICLE_DEV_MODE
if VEHICLE_DEV_MODE then
CreateThread(UpdateVehicleMenu)
end
end)
-- Max Mods
local PERFORMANCE_MOD_INDICES = { 11, 12, 13, 15, 16 }
local function UpgradePerformance(vehicle)
SetVehicleModKit(vehicle, 0)
ToggleVehicleMod(vehicle, 18, true)
SetVehicleFixed(vehicle)
for _, modType in ipairs(PERFORMANCE_MOD_INDICES) do
local maxMod = GetNumVehicleMods(vehicle, modType) - 1
SetVehicleMod(vehicle, modType, maxMod, customWheels)
end
QBCore.Functions.Notify(locale("vehicle_max_modded"), 'success', 7500)
end
RegisterNetEvent('ps-adminmenu:client:maxmodVehicle', function(data)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
if cache.vehicle then
UpgradePerformance(cache.vehicle)
else
QBCore.Functions.Notify(locale("vehicle_not_driver"), 'error', 7500)
end
end)
-- Spawn Personal vehicles
RegisterNetEvent("ps-adminmenu:client:SpawnPersonalVehicle", function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local plate = selectedData['VehiclePlate'].value
local ped = PlayerPedId()
local coords = QBCore.Functions.GetCoords(ped)
local cid = QBCore.Functions.GetPlayerData().citizenid
lib.callback('ps-adminmenu:server:GetVehicleByPlate', false, function(vehModel)
vehicle = vehModel
end, plate)
Wait(100)
QBCore.Functions.TriggerCallback('QBCore:Server:SpawnVehicle', function(vehicle)
local veh = NetToVeh(vehicle)
local props = QBCore.Functions.GetVehicleProperties(veh)
SetEntityHeading(veh, coords.w)
TaskWarpPedIntoVehicle(ped, veh, -1)
SetVehicleModKit(veh, 0)
Wait(100)
QBCore.Functions.SetVehicleProperties(veh, props)
SetVehicleNumberPlateText(veh, plate)
exports[Config.Fuel]:SetFuel(veh, 100.0)
TriggerEvent("vehiclekeys:client:SetOwner", plate)
TriggerEvent('iens:repaira', ped)
TriggerEvent('vehiclemod:client:fixEverything', ped)
end, vehicle, coords, true)
end)
-- Get Vehicle Data
lib.callback.register("ps-adminmenu:client:getvehData", function(vehicle)
lib.requestModel(vehicle)
local coords = vec(GetOffsetFromEntityInWorldCoords(cache.ped, 0.0, 2.0, 0.5), GetEntityHeading(cache.ped) + 90)
local veh = CreateVehicle(vehicle, coords, false, false)
local prop = {}
if DoesEntityExist(veh) then
SetEntityCollision(veh, false, false)
FreezeEntityPosition(veh, true)
prop = QBCore.Functions.GetVehicleProperties(veh)
Wait(500)
DeleteVehicle(veh)
end
return prop
end)

View File

@ -0,0 +1,61 @@
-- Changes the time
RegisterNetEvent('ps-adminmenu:client:ChangeTime', function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local time = selectedData["Time Events"].value
if not time then return end
TriggerServerEvent('qb-weathersync:server:setTime', time, 00)
end)
-- Changes the weather
RegisterNetEvent('ps-adminmenu:client:ChangeWeather', function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local weather = selectedData["Weather"].value
TriggerServerEvent('qb-weathersync:server:setWeather', weather)
end)
RegisterNetEvent('ps-adminmenu:client:copyToClipboard', function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local dropdown = selectedData["Copy Coords"].value
local ped = PlayerPedId()
local string = nil
if dropdown == 'vector2' then
local coords = GetEntityCoords(ped)
local x = QBCore.Shared.Round(coords.x, 2)
local y = QBCore.Shared.Round(coords.y, 2)
string = "vector2(".. x ..", ".. y ..")"
QBCore.Functions.Notify(locale("copy_vector2"), 'success')
elseif dropdown == 'vector3' then
local coords = GetEntityCoords(ped)
local x = QBCore.Shared.Round(coords.x, 2)
local y = QBCore.Shared.Round(coords.y, 2)
local z = QBCore.Shared.Round(coords.z, 2)
string = "vector3(".. x ..", ".. y ..", ".. z ..")"
QBCore.Functions.Notify(locale("copy_vector3"), 'success')
elseif dropdown == 'vector4' then
local coords = GetEntityCoords(ped)
local x = QBCore.Shared.Round(coords.x, 2)
local y = QBCore.Shared.Round(coords.y, 2)
local z = QBCore.Shared.Round(coords.z, 2)
local heading = GetEntityHeading(ped)
local h = QBCore.Shared.Round(heading, 2)
string = "vector4(".. x ..", ".. y ..", ".. z ..", ".. h ..")"
QBCore.Functions.Notify(locale("copy_vector4"), 'success')
elseif dropdown == 'heading' then
local heading = GetEntityHeading(ped)
local h = QBCore.Shared.Round(heading, 2)
string = h
QBCore.Functions.Notify(locale("copy_heading"), 'success')
elseif string == nil then
QBCore.Functions.Notify(locale("empty_input"), 'error')
end
lib.setClipboard(string)
end)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,500 @@
return {
--- female
{ label = "a_f_m_beach_01", value = "female"},
{ label = "a_f_m_bevhills_01", value = "female"},
{ label = "a_f_m_bevhills_02", value = "female"},
{ label = "a_f_m_bodybuild_01", value = "female"},
{ label = "a_f_m_business_02", value = "female"},
{ label = "a_f_m_downtown_01", value = "female"},
{ label = "a_f_m_eastsa_01", value = "female"},
{ label = "a_f_m_eastsa_02", value = "female"},
{ label = "a_f_m_fatbla_01", value = "female"},
{ label = "a_f_m_fatcult_01", value = "female"},
{ label = "a_f_m_fatwhite_01", value = "female"},
{ label = "a_f_m_ktown_01", value = "female"},
{ label = "a_f_m_ktown_02", value = "female"},
{ label = "a_f_m_prolhost_01", value = "female"},
{ label = "a_f_m_salton_01", value = "female"},
{ label = "a_f_m_skidrow_01", value = "female"},
{ label = "a_f_m_soucentmc_01", value = "female"},
{ label = "a_f_m_soucent_01", value = "female"},
{ label = "a_f_m_soucent_02", value = "female"},
{ label = "a_f_m_tourist_01", value = "female"},
{ label = "a_f_m_trampbeac_01", value = "female"},
{ label = "a_f_m_tramp_01", value = "female"},
{ label = "a_f_o_genstreet_01", value = "female"},
{ label = "a_f_o_indian_01", value = "female"},
{ label = "a_f_o_ktown_01", value = "female"},
{ label = "a_f_o_salton_01", value = "female"},
{ label = "a_f_o_soucent_01", value = "female"},
{ label = "a_f_o_soucent_02", value = "female"},
{ label = "a_f_y_beach_01", value = "female"},
{ label = "a_f_y_bevhills_01", value = "female"},
{ label = "a_f_y_bevhills_02", value = "female"},
{ label = "a_f_y_bevhills_03", value = "female"},
{ label = "a_f_y_bevhills_04", value = "female"},
{ label = "a_f_y_business_01", value = "female"},
{ label = "a_f_y_business_02", value = "female"},
{ label = "a_f_y_business_03", value = "female"},
{ label = "a_f_y_business_04", value = "female"},
{ label = "a_f_y_eastsa_01", value = "female"},
{ label = "a_f_y_eastsa_02", value = "female"},
{ label = "a_f_y_eastsa_03", value = "female"},
{ label = "a_f_y_epsilon_01", value = "female"},
{ label = "a_f_y_fitness_01", value = "female"},
{ label = "a_f_y_fitness_02", value = "female"},
{ label = "a_f_y_genhot_01", value = "female"},
{ label = "a_f_y_golfer_01", value = "female"},
{ label = "a_f_y_hiker_01", value = "female"},
{ label = "a_f_y_hipster_01", value = "female"},
{ label = "a_f_y_hipster_02", value = "female"},
{ label = "a_f_y_hipster_03", value = "female"},
{ label = "a_f_y_hipster_04", value = "female"},
{ label = "a_f_y_indian_01", value = "female"},
{ label = "a_f_y_juggalo_01", value = "female"},
{ label = "a_f_y_runner_01", value = "female"},
{ label = "a_f_y_rurmeth_01", value = "female"},
{ label = "a_f_y_scdressy_01", value = "female"},
{ label = "a_f_y_skater_01", value = "female"},
{ label = "a_f_y_soucent_01", value = "female"},
{ label = "a_f_y_soucent_02", value = "female"},
{ label = "a_f_y_soucent_03", value = "female"},
{ label = "a_f_y_tennis_01", value = "female"},
{ label = "a_f_y_tourist_01", value = "female"},
{ label = "a_f_y_tourist_02", value = "female"},
{ label = "a_f_y_vinewood_01", value = "female"},
{ label = "a_f_y_vinewood_02", value = "female"},
{ label = "a_f_y_vinewood_03", value = "female"},
{ label = "a_f_y_vinewood_04", value = "female"},
{ label = "a_f_y_yoga_01", value = "female"},
{ label = "g_f_y_ballas_01", value = "female"},
{ label = "g_f_y_families_01", value = "female"},
{ label = "g_f_y_lost_01", value = "female"},
{ label = "g_f_y_vagos_01", value = "female"},
{ label = "mp_f_deadhooker", value = "female"},
{ label = "mp_f_freemode_01", value = "female"},
{ label = "mp_f_misty_01", value = "female"},
--{ label = "mp_f_stripperlite", value = "female"},
{ label = "mp_s_m_armoured_01", value = "female"},
{ label = "s_f_m_fembarber", value = "female"},
{ label = "s_f_m_maid_01", value = "female"},
{ label = "s_f_m_shop_high", value = "female"},
{ label = "s_f_m_sweatshop_01", value = "female"},
{ label = "s_f_y_airhostess_01", value = "female"},
{ label = "s_f_y_bartender_01", value = "female"},
{ label = "s_f_y_baywatch_01", value = "female"},
{ label = "s_f_y_cop_01", value = "female"},
{ label = "s_f_y_factory_01", value = "female"},
{ label = "s_f_y_hooker_01", value = "female"},
{ label = "s_f_y_hooker_02", value = "female"},
{ label = "s_f_y_hooker_03", value = "female"},
{ label = "s_f_y_migrant_01", value = "female"},
{ label = "s_f_y_movprem_01", value = "female"},
{ label = "ig_kerrymcintosh", value = "female"},
{ label = "ig_janet", value = "female"},
{ label = "ig_jewelass", value = "female"},
{ label = "ig_magenta", value = "female"},
{ label = "ig_marnie", value = "female"},
{ label = "ig_patricia", value = "female"},
{ label = "ig_screen_writer", value = "female"},
{ label = "ig_tanisha", value = "female"},
{ label = "ig_tonya", value = "female"},
{ label = "ig_tracydisanto", value = "female"},
{ label = "u_f_m_corpse_01", value = "female"},
{ label = "u_f_m_miranda", value = "female"},
{ label = "u_f_m_promourn_01", value = "female"},
{ label = "u_f_o_moviestar", value = "female"},
{ label = "u_f_o_prolhost_01", value = "female"},
{ label = "u_f_y_bikerchic", value = "female"},
{ label = "u_f_y_comjane", value = "female"},
{ label = "u_f_y_corpse_01", value = "female"},
{ label = "u_f_y_corpse_02", value = "female"},
{ label = "u_f_y_hotposh_01", value = "female"},
{ label = "u_f_y_jewelass_01", value = "female"},
{ label = "u_f_y_mistress", value = "female"},
{ label = "u_f_y_poppymich", value = "female"},
{ label = "u_f_y_princess", value = "female"},
{ label = "u_f_y_spyactress", value = "female"},
{ label = "ig_amandatownley", value = "female"},
{ label = "ig_ashley", value = "female"},
{ label = "ig_andreas", value = "female"},
{ label = "ig_ballasog", value = "female"},
{ label = "ig_maryannn", value = "female"},
{ label = "ig_maude", value = "female"},
{ label = "ig_michelle", value = "female"},
{ label = "ig_mrs_thornhill", value = "female"},
{ label = "ig_natalia", value = "female"},
{ label = "s_f_y_scrubs_01", value = "female"},
{ label = "s_f_y_sheriff_01", value = "female"},
{ label = "s_f_y_shop_low", value = "female"},
{ label = "s_f_y_shop_mid", value = "female"},
{ label = "s_f_y_stripperlite", value = "female"},
{ label = "s_f_y_stripper_01", value = "female"},
{ label = "s_f_y_stripper_02", value = "female"},
{ label = "ig_mrsphillips", value = "female"},
{ label = "ig_mrs_thornhill", value = "female"},
{ label = "ig_molly", value = "female"},
{ label = "ig_natalia", value = "female"},
{ label = "s_f_y_sweatshop_01", value = "female"},
{ label = "ig_paige", value = "female"},
{ label = "a_f_y_femaleagent", value = "female"},
{ label = "a_f_y_hippie_01", value = "female"},
--- male
{ label = "ig_trafficwarden", value = "male"},
{ label = "ig_bankman", value = "male"},
{ label = "ig_barry", value = "male"},
{ label = "ig_bestmen", value = "male"},
{ label = "ig_beverly", value = "male"},
{ label = "ig_car3guy1", value = "male"},
{ label = "ig_car3guy2", value = "male"},
{ label = "ig_casey", value = "male"},
{ label = "ig_chef", value = "male"},
{ label = "ig_chengsr", value = "male"},
{ label = "ig_chrisformage", value = "male"},
{ label = "ig_clay", value = "male"},
{ label = "ig_claypain", value = "male"},
{ label = "ig_cletus", value = "male"},
{ label = "ig_dale", value = "male"},
{ label = "ig_dreyfuss", value = "male"},
{ label = "ig_fbisuit_01", value = "male"},
{ label = "ig_floyd", value = "male"},
{ label = "ig_groom", value = "male"},
{ label = "ig_hao", value = "male"},
{ label = "ig_hunter", value = "male"},
{ label = "csb_prolsec", value = "male"},
{ label = "ig_jimmydisanto", value = "male"},
{ label = "ig_joeminuteman", value = "male"},
{ label = "ig_josef", value = "male"},
{ label = "ig_josh", value = "male"},
{ label = "ig_lamardavis", value = "male"},
{ label = "ig_lazlow", value = "male"},
{ label = "ig_lestercrest", value = "male"},
{ label = "ig_lifeinvad_01", value = "male"},
{ label = "ig_lifeinvad_02", value = "male"},
{ label = "ig_manuel", value = "male"},
{ label = "ig_milton", value = "male"},
{ label = "ig_mrk", value = "male"},
{ label = "ig_nervousron", value = "male"},
{ label = "ig_nigel", value = "male"},
{ label = "ig_old_man1a", value = "male"},
{ label = "ig_old_man2", value = "male"},
{ label = "ig_oneil", value = "male"},
-- { label = "ig_orleans", value = "male"},
{ label = "ig_ortega", value = "male"},
{ label = "ig_paper", value = "male"},
{ label = "ig_priest", value = "male"},
{ label = "ig_prolsec_02", value = "male"},
{ label = "ig_ramp_gang", value = "male"},
{ label = "ig_ramp_hic", value = "male"},
{ label = "ig_ramp_hipster", value = "male"},
{ label = "ig_ramp_mex", value = "male"},
{ label = "ig_roccopelosi", value = "male"},
{ label = "ig_russiandrunk", value = "male"},
{ label = "ig_siemonyetarian", value = "male"},
{ label = "ig_solomon", value = "male"},
{ label = "ig_stevehains", value = "male"},
{ label = "ig_stretch", value = "male"},
{ label = "ig_talina", value = "male"},
{ label = "ig_taocheng", value = "male"},
{ label = "ig_taostranslator", value = "male"},
{ label = "ig_tenniscoach", value = "male"},
{ label = "ig_terry", value = "male"},
{ label = "ig_tomepsilon", value = "male"},
{ label = "ig_tylerdix", value = "male"},
{ label = "ig_wade", value = "male"},
{ label = "ig_zimbor", value = "male"},
{ label = "s_m_m_paramedic_01", value = "male"},
{ label = "a_m_m_afriamer_01", value = "male"},
{ label = "a_m_m_beach_01", value = "male"},
{ label = "a_m_m_beach_02", value = "male"},
{ label = "a_m_m_bevhills_01", value = "male"},
{ label = "a_m_m_bevhills_02", value = "male"},
{ label = "a_m_m_business_01", value = "male"},
{ label = "a_m_m_eastsa_01", value = "male"},
{ label = "a_m_m_eastsa_02", value = "male"},
{ label = "a_m_m_farmer_01", value = "male"},
{ label = "a_m_m_fatlatin_01", value = "male"},
{ label = "a_m_m_genfat_01", value = "male"},
{ label = "a_m_m_genfat_02", value = "male"},
{ label = "a_m_m_golfer_01", value = "male"},
{ label = "a_m_m_hasjew_01", value = "male"},
{ label = "a_m_m_hillbilly_01", value = "male"},
{ label = "a_m_m_hillbilly_02", value = "male"},
{ label = "a_m_m_indian_01", value = "male"},
{ label = "a_m_m_ktown_01", value = "male"},
{ label = "a_m_m_malibu_01", value = "male"},
{ label = "a_m_m_mexcntry_01", value = "male"},
{ label = "a_m_m_mexlabor_01", value = "male"},
{ label = "a_m_m_og_boss_01", value = "male"},
{ label = "a_m_m_paparazzi_01", value = "male"},
{ label = "a_m_m_polynesian_01", value = "male"},
{ label = "a_m_m_prolhost_01", value = "male"},
{ label = "a_m_m_rurmeth_01", value = "male"},
{ label = "a_m_m_salton_01", value = "male"},
{ label = "a_m_m_salton_02", value = "male"},
{ label = "a_m_m_salton_03", value = "male"},
{ label = "a_m_m_salton_04", value = "male"},
{ label = "a_m_m_skater_01", value = "male"},
{ label = "a_m_m_skidrow_01", value = "male"},
{ label = "a_m_m_socenlat_01", value = "male"},
{ label = "a_m_m_soucent_01", value = "male"},
{ label = "a_m_m_soucent_02", value = "male"},
{ label = "a_m_m_soucent_03", value = "male"},
{ label = "a_m_m_soucent_04", value = "male"},
{ label = "a_m_m_stlat_02", value = "male"},
{ label = "a_m_m_tennis_01", value = "male"},
{ label = "a_m_m_tourist_01", value = "male"},
{ label = "a_m_m_trampbeac_01", value = "male"},
{ label = "a_m_m_tramp_01", value = "male"},
{ label = "a_m_m_tranvest_01", value = "male"},
{ label = "a_m_m_tranvest_02", value = "male"},
{ label = "a_m_o_beach_01", value = "male"},
{ label = "a_m_o_genstreet_01", value = "male"},
{ label = "a_m_o_ktown_01", value = "male"},
{ label = "a_m_o_salton_01", value = "male"},
{ label = "a_m_o_soucent_01", value = "male"},
{ label = "a_m_o_soucent_02", value = "male"},
{ label = "a_m_o_soucent_03", value = "male"},
{ label = "a_m_o_tramp_01", value = "male"},
{ label = "a_m_y_beachvesp_01", value = "male"},
{ label = "a_m_y_beachvesp_02", value = "male"},
{ label = "a_m_y_beach_01", value = "male"},
{ label = "a_m_y_beach_02", value = "male"},
{ label = "a_m_y_beach_03", value = "male"},
{ label = "a_m_y_bevhills_01", value = "male"},
{ label = "a_m_y_bevhills_02", value = "male"},
{ label = "a_m_y_breakdance_01", value = "male"},
{ label = "a_m_y_busicas_01", value = "male"},
{ label = "a_m_y_business_01", value = "male"},
{ label = "a_m_y_business_02", value = "male"},
{ label = "a_m_y_business_03", value = "male"},
{ label = "a_m_y_cyclist_01", value = "male"},
{ label = "a_m_y_dhill_01", value = "male"},
{ label = "a_m_y_downtown_01", value = "male"},
{ label = "a_m_y_eastsa_01", value = "male"},
{ label = "a_m_y_eastsa_02", value = "male"},
{ label = "a_m_y_epsilon_01", value = "male"},
{ label = "a_m_y_epsilon_02", value = "male"},
{ label = "a_m_y_gay_01", value = "male"},
{ label = "a_m_y_gay_02", value = "male"},
{ label = "a_m_y_genstreet_01", value = "male"},
{ label = "a_m_y_genstreet_02", value = "male"},
{ label = "a_m_y_golfer_01", value = "male"},
{ label = "a_m_y_hasjew_01", value = "male"},
{ label = "a_m_y_hiker_01", value = "male"},
{ label = "a_m_y_hipster_01", value = "male"},
{ label = "a_m_y_hipster_02", value = "male"},
{ label = "a_m_y_hipster_03", value = "male"},
{ label = "a_m_y_indian_01", value = "male"},
{ label = "a_m_y_jetski_01", value = "male"},
{ label = "a_m_y_juggalo_01", value = "male"},
{ label = "a_m_y_ktown_01", value = "male"},
{ label = "a_m_y_ktown_02", value = "male"},
{ label = "a_m_y_latino_01", value = "male"},
{ label = "a_m_y_methhead_01", value = "male"},
{ label = "a_m_y_mexthug_01", value = "male"},
{ label = "a_m_y_motox_01", value = "male"},
{ label = "a_m_y_motox_02", value = "male"},
{ label = "a_m_y_musclbeac_01", value = "male"},
{ label = "a_m_y_musclbeac_02", value = "male"},
{ label = "a_m_y_polynesian_01", value = "male"},
{ label = "a_m_y_roadcyc_01", value = "male"},
{ label = "a_m_y_runner_01", value = "male"},
{ label = "a_m_y_runner_02", value = "male"},
{ label = "a_m_y_salton_01", value = "male"},
{ label = "a_m_y_skater_01", value = "male"},
{ label = "a_m_y_skater_02", value = "male"},
{ label = "a_m_y_soucent_01", value = "male"},
{ label = "a_m_y_soucent_02", value = "male"},
{ label = "a_m_y_soucent_03", value = "male"},
{ label = "a_m_y_soucent_04", value = "male"},
{ label = "a_m_y_stbla_01", value = "male"},
{ label = "a_m_y_stbla_02", value = "male"},
{ label = "a_m_y_stlat_01", value = "male"},
{ label = "a_m_y_stwhi_01", value = "male"},
{ label = "a_m_y_stwhi_02", value = "male"},
{ label = "a_m_y_sunbathe_01", value = "male"},
{ label = "a_m_y_surfer_01", value = "male"},
{ label = "a_m_y_vindouche_01", value = "male"},
{ label = "a_m_y_vinewood_01", value = "male"},
{ label = "a_m_y_vinewood_02", value = "male"},
{ label = "a_m_y_vinewood_03", value = "male"},
{ label = "a_m_y_vinewood_04", value = "male"},
{ label = "a_m_y_yoga_01", value = "male"},
{ label = "g_m_m_armboss_01", value = "male"},
{ label = "g_m_m_armgoon_01", value = "male"},
{ label = "g_m_m_armlieut_01", value = "male"},
{ label = "g_m_m_chemwork_01", value = "male"},
{ label = "g_m_m_chiboss_01", value = "male"},
{ label = "g_m_m_chicold_01", value = "male"},
{ label = "g_m_m_chigoon_01", value = "male"},
{ label = "g_m_m_chigoon_02", value = "male"},
{ label = "g_m_m_korboss_01", value = "male"},
{ label = "g_m_m_mexboss_01", value = "male"},
{ label = "g_m_m_mexboss_02", value = "male"},
{ label = "g_m_y_armgoon_02", value = "male"},
{ label = "g_m_y_azteca_01", value = "male"},
{ label = "g_m_y_ballaeast_01", value = "male"},
{ label = "g_m_y_ballaorig_01", value = "male"},
{ label = "g_m_y_ballasout_01", value = "male"},
{ label = "g_m_y_famca_01", value = "male"},
{ label = "g_m_y_famdnf_01", value = "male"},
{ label = "g_m_y_famfor_01", value = "male"},
{ label = "g_m_y_korean_01", value = "male"},
{ label = "g_m_y_korean_02", value = "male"},
{ label = "g_m_y_korlieut_01", value = "male"},
{ label = "g_m_y_lost_01", value = "male"},
{ label = "g_m_y_lost_02", value = "male"},
{ label = "g_m_y_lost_03", value = "male"},
{ label = "g_m_y_mexgang_01", value = "male"},
{ label = "g_m_y_mexgoon_01", value = "male"},
{ label = "g_m_y_mexgoon_02", value = "male"},
{ label = "g_m_y_mexgoon_03", value = "male"},
{ label = "g_m_y_pologoon_01", value = "male"},
{ label = "g_m_y_pologoon_02", value = "male"},
{ label = "g_m_y_salvaboss_01", value = "male"},
{ label = "g_m_y_salvagoon_01", value = "male"},
{ label = "g_m_y_salvagoon_02", value = "male"},
{ label = "g_m_y_salvagoon_03", value = "male"},
{ label = "g_m_y_strpunk_01", value = "male"},
{ label = "g_m_y_strpunk_02", value = "male"},
{ label = "mp_m_claude_01", value = "male"},
{ label = "mp_m_exarmy_01", value = "male"},
{ label = "mp_m_shopkeep_01", value = "male"},
{ label = "s_m_m_ammucountry", value = "male"},
{ label = "s_m_m_autoshop_01", value = "male"},
{ label = "s_m_m_autoshop_02", value = "male"},
{ label = "s_m_m_bouncer_01", value = "male"},
{ label = "s_m_m_chemsec_01", value = "male"},
{ label = "s_m_m_cntrybar_01", value = "male"},
{ label = "s_m_m_dockwork_01", value = "male"},
{ label = "s_m_m_doctor_01", value = "male"},
{ label = "s_m_m_fiboffice_01", value = "male"},
{ label = "s_m_m_fiboffice_02", value = "male"},
{ label = "s_m_m_gaffer_01", value = "male"},
{ label = "s_m_m_gardener_01", value = "male"},
{ label = "s_m_m_gentransport", value = "male"},
{ label = "s_m_m_hairdress_01", value = "male"},
{ label = "s_m_m_highsec_01", value = "male"},
{ label = "s_m_m_highsec_02", value = "male"},
{ label = "s_m_m_janitor", value = "male"},
{ label = "s_m_m_lathandy_01", value = "male"},
{ label = "s_m_m_lifeinvad_01", value = "male"},
{ label = "s_m_m_linecook", value = "male"},
{ label = "s_m_m_lsmetro_01", value = "male"},
{ label = "s_m_m_mariachi_01", value = "male"},
{ label = "s_m_m_marine_01", value = "male"},
{ label = "s_m_m_marine_02", value = "male"},
{ label = "s_m_m_migrant_01", value = "male"},
{ label = "s_m_m_movalien_01", value = "male"},
{ label = "s_m_m_movprem_01", value = "male"},
{ label = "s_m_m_movspace_01", value = "male"},
{ label = "s_m_m_pilot_01", value = "male"},
{ label = "s_m_m_pilot_02", value = "male"},
{ label = "s_m_m_postal_01", value = "male"},
{ label = "s_m_m_postal_02", value = "male"},
{ label = "s_m_m_scientist_01", value = "male"},
{ label = "s_m_m_security_01", value = "male"},
{ label = "s_m_m_strperf_01", value = "male"},
{ label = "s_m_m_strpreach_01", value = "male"},
{ label = "s_m_m_strvend_01", value = "male"},
{ label = "s_m_m_trucker_01", value = "male"},
{ label = "s_m_m_ups_01", value = "male"},
{ label = "s_m_m_ups_02", value = "male"},
{ label = "s_m_o_busker_01", value = "male"},
{ label = "s_m_y_airworker", value = "male"},
{ label = "s_m_y_ammucity_01", value = "male"},
{ label = "s_m_y_armymech_01", value = "male"},
{ label = "s_m_y_autopsy_01", value = "male"},
{ label = "s_m_y_barman_01", value = "male"},
{ label = "s_m_y_baywatch_01", value = "male"},
{ label = "s_m_y_blackops_01", value = "male"},
{ label = "s_m_y_blackops_02", value = "male"},
{ label = "s_m_y_busboy_01", value = "male"},
{ label = "s_m_y_chef_01", value = "male"},
{ label = "s_m_y_clown_01", value = "male"},
{ label = "s_m_y_construct_01", value = "male"},
{ label = "s_m_y_construct_02", value = "male"},
{ label = "s_m_y_cop_01", value = "male"},
{ label = "s_m_y_dealer_01", value = "male"},
{ label = "s_m_y_devinsec_01", value = "male"},
{ label = "s_m_y_dockwork_01", value = "male"},
{ label = "s_m_y_doorman_01", value = "male"},
{ label = "s_m_y_dwservice_01", value = "male"},
{ label = "s_m_y_dwservice_02", value = "male"},
{ label = "s_m_y_factory_01", value = "male"},
{ label = "s_m_y_garbage", value = "male"},
{ label = "s_m_y_grip_01", value = "male"},
{ label = "s_m_y_marine_01", value = "male"},
{ label = "s_m_y_marine_02", value = "male"},
{ label = "s_m_y_marine_03", value = "male"},
{ label = "s_m_y_mime", value = "male"},
{ label = "s_m_y_pestcont_01", value = "male"},
{ label = "s_m_y_pilot_01", value = "male"},
{ label = "s_m_y_prismuscl_01", value = "male"},
{ label = "s_m_y_prisoner_01", value = "male"},
{ label = "s_m_y_robber_01", value = "male"},
{ label = "s_m_y_shop_mask", value = "male"},
{ label = "s_m_y_strvend_01", value = "male"},
{ label = "s_m_y_uscg_01", value = "male"},
{ label = "s_m_y_valet_01", value = "male"},
{ label = "s_m_y_waiter_01", value = "male"},
{ label = "s_m_y_winclean_01", value = "male"},
{ label = "s_m_y_xmech_01", value = "male"},
{ label = "s_m_y_xmech_02", value = "male"},
{ label = "u_m_m_aldinapoli", value = "male"},
{ label = "u_m_m_bankman", value = "male"},
{ label = "u_m_m_bikehire_01", value = "male"},
{ label = "u_m_m_fibarchitect", value = "male"},
{ label = "u_m_m_filmdirector", value = "male"},
{ label = "u_m_m_glenstank_01", value = "male"},
{ label = "u_m_m_griff_01", value = "male"},
{ label = "u_m_m_jesus_01", value = "male"},
{ label = "u_m_m_jewelsec_01", value = "male"},
{ label = "u_m_m_jewelthief", value = "male"},
{ label = "u_m_m_markfost", value = "male"},
{ label = "u_m_m_partytarget", value = "male"},
{ label = "u_m_m_prolsec_01", value = "male"},
{ label = "u_m_m_promourn_01", value = "male"},
{ label = "u_m_m_rivalpap", value = "male"},
{ label = "u_m_m_spyactor", value = "male"},
{ label = "u_m_m_willyfist", value = "male"},
{ label = "u_m_o_finguru_01", value = "male"},
{ label = "u_m_o_taphillbilly", value = "male"},
{ label = "u_m_o_tramp_01", value = "male"},
{ label = "u_m_y_abner", value = "male"},
{ label = "u_m_y_antonb", value = "male"},
{ label = "u_m_y_babyd", value = "male"},
{ label = "u_m_y_baygor", value = "male"},
{ label = "u_m_y_burgerdrug_01", value = "male"},
{ label = "u_m_y_chip", value = "male"},
{ label = "u_m_y_cyclist_01", value = "male"},
{ label = "u_m_y_fibmugger_01", value = "male"},
{ label = "u_m_y_guido_01", value = "male"},
{ label = "u_m_y_gunvend_01", value = "male"},
{ label = "u_m_y_imporage", value = "male"},
{ label = "u_m_y_mani", value = "male"},
{ label = "u_m_y_militarybum", value = "male"},
{ label = "u_m_y_paparazzi", value = "male"},
{ label = "u_m_y_party_01", value = "male"},
{ label = "u_m_y_pogo_01", value = "male"},
{ label = "u_m_y_prisoner_01", value = "male"},
{ label = "u_m_y_proldriver_01", value = "male"},
{ label = "u_m_y_rsranger_01", value = "male"},
{ label = "u_m_y_sbike", value = "male"},
{ label = "u_m_y_staggrm_01", value = "male"},
{ label = "u_m_y_tattoo_01", value = "male"},
{ label = "u_m_y_zombie_01", value = "male"},
{ label = "u_m_y_hippie_01", value = "male"},
{ label = "a_m_y_hippy_01", value = "male"},
{ label = "a_m_y_stbla_m", value = "male"},
{ label = "ig_terry_m", value = "male"},
{ label = "a_m_m_ktown_m", value = "male"},
{ label = "a_m_y_skater_m", value = "male"},
{ label = "u_m_y_coop", value = "male"},
{ label = "ig_car3guy1_m", value = "male"},
{ label = "tony", value = "male"},
{ label = "g_m_m_chigoon_02_m", value = "male"},
{ label = "a_m_o_acult_01", value = "male"}
}

View File

@ -0,0 +1,36 @@
fx_version 'cerulean'
game "gta5"
author "Project Sloth & OK1ez"
version '1.1.2'
description 'Admin Menu'
repository 'https://github.com/Project-Sloth/ps-adminmenu'
lua54 'yes'
ui_page 'html/index.html'
-- ui_page 'https://localhost:5173/' --for dev
client_script {
'client/**',
}
server_script {
"server/**",
"@oxmysql/lib/MySQL.lua",
}
shared_script {
'@ox_lib/init.lua',
"shared/**",
}
files {
'html/**',
'data/ped.lua',
'data/object.lua',
'locales/da.json',
}
ox_lib 'locale' -- v3.8.0 or above

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- FontAwesome Icons Import -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css">
<!-- Material Icons Import -->
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<!-- Material Design Icons Import -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font/css/materialdesignicons.min.css">
<!-- Line Awesome Icons Import -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/line-awesome/dist/line-awesome/css/line-awesome.min.css">
<!-- Boostrap Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.min.css">
<title>ps-adminmenu</title>
<script type="module" crossorigin src="./index.js"></script>
<link rel="stylesheet" href="./index.css">
</head>
<body>
<div id="app"></div>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,97 @@
{
"already_plate": "Nummerpladen er allerede i brug",
"amount_max": "Maksimum beløb er 999.999,-",
"ban_expires": "Ban udløber: ",
"ban_perm": "Du er blevet permanent udelukket!",
"banreason": "Årsag: %s, indtil %s",
"banned": "Du er blevet udelukket!",
"blackout": "Blackout er %s",
"blips_activated": "Blips aktiveret",
"blips_deactivated": "Blips deaktiveret",
"body_health": "HP: ",
"bucket_get": "Spiller %s er i bucket: %s",
"bucket_set": "Routing sat til bucket: %s",
"bucket_set_for_target": "Routing sat til bucket: %s til bucket: %s",
"cannot_store_veh": "Kan ikke opbevare denne bil i din garage",
"cant_spectate_yourself": "Du kan ikke se dig selv",
"command_admin_desc": "Toggle Admin Menu",
"command_noclip_desc": "Toggle NoClip",
"copy_heading": "Kopierede vinkel",
"copy_vector2": "Kopierede vector2",
"copy_vector3": "Kopierede vector3",
"copy_vector4": "Kopierede vector4",
"deFrozen": "Du har sat %s fri",
"empty_input": "Input var tomt.",
"eng_health": "Motorhelbred: ",
"entered_vehicle": "Gik ind i køretøj",
"ent_id": "Entity ID: ",
"explode_player": "Personen sprang i luften :P",
"Frozen": "%s er blevet frosset",
"gangset": "Du har givet %s banden: %s med grad %s",
"give_item": "Gav %s til %s",
"give_item_all": "Gav %s til alle spillere",
"give_money": "Gav %s %s",
"give_money_all": "Gav %s til alle spillere",
"give_money_all_crypto": "Gav %s Crypto/s til alle spillere",
"give_money_crypto": "Gav %s Crypto/s til %s",
"givecar.plates_alreadyused": "Kan ikke give køretøj. Nummerplade %s er allerede i brug på et andet køretøj",
"givecar.success.source": "Gav køretøjet %s til %s.",
"givecar.success.target": "Du har modtaget et nyt køretøj med nummerplade %s. Du kan finde den ved %s.",
"godmode": "Gudmode er %s",
"hash": "Hash: ",
"inf_ammo_toggled": "Uendelig ammunition slået til",
"invisible": "Usynlighed: %s",
"invcleared": "%s's inventar blev tømt",
"jobset": "Du har givet %s jobbet: %s med grad %s",
"kicked": "Du blev kicked!",
"model": "Model: ",
"names_activated": "Navne aktiveret",
"names_deactivated": "Navne deaktiveret",
"net_id": "Net ID: ",
"net_id_not_registered": "Net ID ikke registreret",
"new_staffchat": "Ny Staff-besked",
"no_free_seats": "Der er ingen ledige pladser",
"no_perms": "Du har ikke tilladelse til at udføre denne handling",
"no_waypoint": "Intet waypoint sat",
"no_weapon": "Spilleren har ikke en pistol.",
"noclip_disabled": "No-clip slået fra",
"noclip_enabled": "No-clip slået til",
"not_enough_money": "Ikke nok penge til at fjerne penge fra spilleren",
"not_in_veh": "Du er ikke i et køretøj..",
"not_in_vehicle": "Du er ikke i et køretøj",
"not_online": "Spilleren er ikke online",
"ped_coords": "Ped Koordinater: ",
"plate_max": "Nummerpladen kan maks være 8 tegn",
"plate_invalid": "Ugyldig nummerplade",
"player_not_found": "Spilleren blev ikke fundet",
"player_not_in_veh": "Spilleren er ikke i et køretøj",
"player_perms": "%s fik [ %s ] tilladelser.",
"playerbanned": "Du har udelukket %s i %s med årsag: %s",
"playerdrunk": "Gjorde spilleren fuld: ",
"reason": "Årsag: ",
"refueled_vehicle": "Tankede køretøjet op",
"restarted_resource": "Genstartede ressource",
"removed_stress_player": "Fjernede stress fra spiller",
"set_on_fire": "Person sat i brand :P",
"set_wepaon_ammo": "Gav %s ammunition!",
"started_resource": "Startede ressource",
"status_title": "CFX Status",
"stopped_resource": "Stoppede ressource",
"state_changed": "Du har sat køretøjets tilstand.",
"take_money": "Fjernede %s fra %s",
"take_money_crypto": "Fjernede %s Crypto/s fra %s",
"target_same_bucket": "Prøvede at placere %s i samme bucket",
"teleported_waypoint": "Teleporterede til waypoint.",
"toggled_cuffs": "Toggled håndjern",
"toggle_dev": "Toggled Dev Mode",
"tp_error": "Fejl under teleportering.",
"u_veh_owner": "Dette køretøj er allerede dit..",
"veh_fixed": "Køretøj repareret for %s",
"veh_owner": "Køretøjet er nu dit!",
"vehicle_dev_data": "Køretøjs-information",
"vehicle_max_modded": "Køretøjet er top tunet",
"vehicle_not_driver": "Ikke i førersædet",
"warned": "Du er blevet advaret ",
"warngiven": "Advaret: ",
"weatherType": "Vejret er ændret til: %s"
}

View File

@ -0,0 +1,24 @@
local messages = {}
-- Staff Chat
RegisterNetEvent('ps-adminmenu:server:sendMessageServer', function(message, citizenid, fullname)
if not CheckPerms('mod') then return end
local time = os.time() * 1000
local players = QBCore.Functions.GetPlayers()
for i = 1, #players, 1 do
local player = players[i]
if QBCore.Functions.IsOptin(player) then
QBCore.Functions.Notify(player, locale("new_staffchat", 'info', 7500))
end
end
messages[#messages + 1] = { message = message, citizenid = citizenid, fullname = fullname, time = time }
end)
lib.callback.register('ps-adminmenu:callback:GetMessages', function()
if not CheckPerms('mod') then return {} end
return messages
end)

View File

@ -0,0 +1,109 @@
-- Clear Inventory
RegisterNetEvent('ps-adminmenu:server:ClearInventory', function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local src = source
local player = selectedData["Player"].value
local Player = QBCore.Functions.GetPlayer(player)
if not Player then
return QBCore.Functions.Notify(source, locale("not_online"), 'error', 7500)
end
if Config.Inventory == 'ox_inventory' then
exports.ox_inventory:ClearInventory(player)
else
exports[Config.Inventory]:ClearInventory(player, nil)
end
QBCore.Functions.Notify(src,
locale("invcleared", Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname),
'success', 7500)
end)
-- Clear Inventory Offline
RegisterNetEvent('ps-adminmenu:server:ClearInventoryOffline', function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local src = source
local citizenId = selectedData["Citizen ID"].value
local Player = QBCore.Functions.GetPlayerByCitizenId(citizenId)
if Player then
if Config.Inventory == 'ox_inventory' then
exports.ox_inventory:ClearInventory(Player.PlayerData.source)
else
exports[Config.Inventory]:ClearInventory(Player.PlayerData.source, nil)
end
QBCore.Functions.Notify(src,
locale("invcleared", Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname),
'success', 7500)
else
MySQL.Async.fetchAll("SELECT * FROM players WHERE citizenid = @citizenid", { ['@citizenid'] = citizenId },
function(result)
if result and result[1] then
MySQL.Async.execute("UPDATE players SET inventory = '{}' WHERE citizenid = @citizenid",
{ ['@citizenid'] = citizenId })
QBCore.Functions.Notify(src, "Spillerens inventar blev tømt", 'success', 7500)
else
QBCore.Functions.Notify(src, locale("player_not_found"), 'error', 7500)
end
end)
end
end)
-- Open Inv [ox side]
RegisterNetEvent('ps-adminmenu:server:OpenInv', function(data)
exports.ox_inventory:forceOpenInventory(source, 'player', data)
end)
-- Open Stash [ox side]
RegisterNetEvent('ps-adminmenu:server:OpenStash', function(data)
exports.ox_inventory:forceOpenInventory(source, 'stash', data)
end)
-- Open Trunk [ox side]
RegisterNetEvent('ps-adminmenu:server:OpenTrunk', function(data)
exports.ox_inventory:forceOpenInventory(source, 'trunk', data)
end)
-- Give Item
RegisterNetEvent('ps-adminmenu:server:GiveItem', function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local target = selectedData["Player"].value
local item = selectedData["Item"].value
local amount = selectedData["Amount"].value
local Player = QBCore.Functions.GetPlayer(target)
if not item or not amount then return end
if not Player then
return QBCore.Functions.Notify(source, locale("not_online"), 'error', 7500)
end
Player.Functions.AddItem(item, amount)
QBCore.Functions.Notify(source,
locale("give_item", tonumber(amount) .. " " .. item,
Player.PlayerData.charinfo.firstname .. " " .. Player.PlayerData.charinfo.lastname), "success", 7500)
end)
-- Give Item to All
RegisterNetEvent('ps-adminmenu:server:GiveItemAll', function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local item = selectedData["Item"].value
local amount = selectedData["Amount"].value
local players = QBCore.Functions.GetPlayers()
if not item or not amount then return end
for _, id in pairs(players) do
local Player = QBCore.Functions.GetPlayer(id)
Player.Functions.AddItem(item, amount)
QBCore.Functions.Notify(source, locale("give_item_all", amount .. " " .. item), "success", 7500)
end
end)

View File

@ -0,0 +1,9 @@
QBCore = exports['qb-core']:GetCoreObject()
lib.addCommand('admin', {
help = 'Åben admin-menuen',
restricted = 'qbcore.mod'
}, function(source)
TriggerClientEvent('ps-adminmenu:client:OpenUI', source)
end)
-- Callbacks

View File

@ -0,0 +1,330 @@
-- Ban Player
RegisterNetEvent('ps-adminmenu:server:BanPlayer', function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local player = selectedData["Player"].value
local reason = selectedData["Reason"].value or ""
local time = selectedData["Duration"].value
local banTime = tonumber(os.time() + time)
local timeTable = os.date('*t', banTime)
MySQL.insert('INSERT INTO bans (name, license, discord, ip, reason, expire, bannedby) VALUES (?, ?, ?, ?, ?, ?, ?)',
{ GetPlayerName(player), QBCore.Functions.GetIdentifier(player, 'license'), QBCore.Functions.GetIdentifier(
player, 'discord'), 'ip:0.0.0.0', reason, banTime, GetPlayerName(source) })
if time == 2147483647 then
DropPlayer(player, locale("banned") .. '\n' .. locale("reason") .. reason .. locale("ban_perm"))
else
DropPlayer(player,
locale("banned") ..
'\n' ..
locale("reason") ..
reason ..
'\n' ..
locale("ban_expires") ..
timeTable['day'] ..
'/' .. timeTable['month'] .. '/' .. timeTable['year'] .. ' ' .. timeTable['hour'] .. ':' .. timeTable['min'])
end
QBCore.Functions.Notify(source, locale("playerbanned", player, banTime, reason), 'success', 7500)
end)
RegisterNetEvent('ps-adminmenu:server:ServerMessage' , function(data, selectedData)
local target = -1
local from = 'Staff'
local message = selectedData["Message"].value
local time = selectedData["Duration"].value or 10
if message == "" then
return
end
if selectedData["Player"] then
target = selectedData["Player"].value
from = GetPlayerName(source)
QBCore.Functions.Notify(source, 'Besked sendt til '..GetPlayerName(target).."!")
end
TriggerClientEvent('ox_lib:notify', -1, {
id = 'AdminNotification',
title = 'Info fra '..from,
description = message,
-- type = 'inform',
duration = (time * 1000),
position = 'top',
iconColor = '#ffffff',
iconAnimation = 'beatFade',
style = {
backgroundColor = '#3377ff',
color = '#ffffff',
['.description'] = {
color = '#ffffff'
}
},
})
TriggerClientEvent('InteractSound_CL:PlayOnAll', target, "announcement", 0.4)
end)
-- Warn Player
RegisterNetEvent('ps-adminmenu:server:WarnPlayer', function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local targetId = selectedData["Player"].value
local target = QBCore.Functions.GetPlayer(targetId)
local reason = selectedData["Reason"].value
local sender = QBCore.Functions.GetPlayer(source)
local warnId = 'ADVAR-' .. math.random(1000, 9999)
if target ~= nil then
QBCore.Functions.Notify(target.PlayerData.source,
locale("warned") .. ", for: " .. locale("reason") .. ": " .. reason, 'info', 10000)
QBCore.Functions.Notify(source,
locale("warngiven") .. GetPlayerName(target.PlayerData.source) .. ", for: " .. reason)
MySQL.insert('INSERT INTO player_warns (senderIdentifier, targetIdentifier, reason, warnId) VALUES (?, ?, ?, ?)',
{
sender.PlayerData.license,
target.PlayerData.license,
reason,
warnId
})
else
TriggerClientEvent('QBCore:Notify', source, locale("not_online"), 'error')
end
end)
RegisterNetEvent('ps-adminmenu:server:KickPlayer', function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local src = source
local target = QBCore.Functions.GetPlayer(selectedData["Player"].value)
local reason = selectedData["Reason"].value
if not target then
QBCore.Functions.Notify(src, locale("not_online"), 'error', 7500)
return
end
DropPlayer(target.PlayerData.source, locale("kicked") .. '\n' .. locale("reason") .. reason)
end)
-- Revive Player
RegisterNetEvent('ps-adminmenu:server:Revive', function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local player = selectedData["Player"].value
TriggerClientEvent('hospital:client:Revive', player)
end)
-- Revive All
RegisterNetEvent('ps-adminmenu:server:ReviveAll', function(data)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
TriggerClientEvent('hospital:client:Revive', -1)
end)
-- Revive Radius
RegisterNetEvent('ps-adminmenu:server:ReviveRadius', function(data)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local src = source
local ped = GetPlayerPed(src)
local pos = GetEntityCoords(ped)
local players = QBCore.Functions.GetPlayers()
for k, v in pairs(players) do
local target = GetPlayerPed(v)
local targetPos = GetEntityCoords(target)
local dist = #(pos - targetPos)
if dist < 15.0 then
TriggerClientEvent("hospital:client:Revive", v)
end
end
end)
-- Set RoutingBucket
RegisterNetEvent('ps-adminmenu:server:SetBucket', function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local src = source
local player = selectedData["Player"].value
local bucket = selectedData["Bucket"].value
local currentBucket = GetPlayerRoutingBucket(player)
if bucket == currentBucket then
return QBCore.Functions.Notify(src, locale("target_same_bucket", player), 'error', 7500)
end
SetPlayerRoutingBucket(player, bucket)
QBCore.Functions.Notify(src, locale("bucket_set_for_target", player, bucket), 'success', 7500)
end)
-- Get RoutingBucket
RegisterNetEvent('ps-adminmenu:server:GetBucket', function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local src = source
local player = selectedData["Player"].value
local currentBucket = GetPlayerRoutingBucket(player)
QBCore.Functions.Notify(src, locale("bucket_get", player, currentBucket), 'success', 7500)
end)
-- Give Money
RegisterNetEvent('ps-adminmenu:server:GiveMoney', function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local src = source
local target, amount, moneyType = selectedData["Player"].value, selectedData["Amount"].value,
selectedData["Type"].value
local Player = QBCore.Functions.GetPlayer(tonumber(target))
if Player == nil then
return QBCore.Functions.Notify(src, locale("not_online"), 'error', 7500)
end
Player.Functions.AddMoney(tostring(moneyType), tonumber(amount))
QBCore.Functions.Notify(src,
locale((moneyType == "crypto" and "give_money_crypto" or "give_money"), tonumber(amount),
Player.PlayerData.charinfo.firstname .. " " .. Player.PlayerData.charinfo.lastname), "success")
end)
-- Give Money to all
RegisterNetEvent('ps-adminmenu:server:GiveMoneyAll', function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local src = source
local amount, moneyType = selectedData["Amount"].value, selectedData["Type"].value
local players = QBCore.Functions.GetPlayers()
for _, v in pairs(players) do
local Player = QBCore.Functions.GetPlayer(tonumber(v))
Player.Functions.AddMoney(tostring(moneyType), tonumber(amount))
QBCore.Functions.Notify(src,
locale((moneyType == "crypto" and "give_money_all_crypto" or "give_money_all"), tonumber(amount)), "success")
end
end)
-- Take Money
RegisterNetEvent('ps-adminmenu:server:TakeMoney', function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local src = source
local target, amount, moneyType = selectedData["Player"].value, selectedData["Amount"].value,
selectedData["Type"].value
local Player = QBCore.Functions.GetPlayer(tonumber(target))
if Player == nil then
return QBCore.Functions.Notify(src, locale("not_online"), 'error', 7500)
end
if Player.PlayerData.money[moneyType] >= tonumber(amount) then
Player.Functions.RemoveMoney(moneyType, tonumber(amount), "state-fees")
else
QBCore.Functions.Notify(src, locale("not_enough_money"), "primary")
end
QBCore.Functions.Notify(src,
locale((moneyType == "crypto" and "take_money_crypto" or "take_money"), tonumber(amount) .. ",-",
Player.PlayerData.charinfo.firstname .. " " .. Player.PlayerData.charinfo.lastname), "success")
end)
-- Blackout
local Blackout = false
RegisterNetEvent('ps-adminmenu:server:ToggleBlackout', function(data)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
Blackout = not Blackout
local src = source
if Blackout then
TriggerClientEvent('QBCore:Notify', src, locale("blackout", "enabled"), 'primary')
while Blackout do
Wait(0)
exports["qb-weathersync"]:setBlackout(true)
end
exports["qb-weathersync"]:setBlackout(false)
TriggerClientEvent('QBCore:Notify', src, locale("blackout", "disabled"), 'primary')
end
end)
-- Toggle Cuffs
RegisterNetEvent('ps-adminmenu:server:CuffPlayer', function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local target = selectedData["Player"].value
TriggerClientEvent('ps-adminmenu:client:ToggleCuffs', target)
QBCore.Functions.Notify(source, locale("toggled_cuffs"), 'success')
end)
-- Give Clothing Menu
RegisterNetEvent('ps-adminmenu:server:ClothingMenu', function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local src = source
local target = tonumber(selectedData["Player"].value)
if target == nil then
return QBCore.Functions.Notify(src, locale("not_online"), 'error', 7500)
end
if target == src then
TriggerClientEvent("ps-adminmenu:client:CloseUI", src)
end
TriggerClientEvent('qb-clothing:client:openMenu', target)
end)
-- Force to character setup
RegisterNetEvent('ps-adminmenu:server:CharacterCreation', function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local src = source
local target = tonumber(selectedData["Player"].value)
if target == nil then
return QBCore.Functions.Notify(src, locale("not_online"), 'error', 7500)
end
if target == src then
TriggerClientEvent("ps-adminmenu:client:CloseUI", src)
end
TriggerClientEvent('hp_charcreator:openCreator', target, nil, true)
end)
-- Set Ped
RegisterNetEvent("ps-adminmenu:server:setPed", function(data, selectedData)
local src = source
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then
QBCore.Functions.Notify(src, locale("no_perms"), "error", 5000)
return
end
local ped = selectedData["Ped Models"].label
local tsrc = selectedData["Player"].value
local Player = QBCore.Functions.GetPlayer(tsrc)
if not Player then
QBCore.Functions.Notify(locale("not_online"), "error", 5000)
return
end
TriggerClientEvent("ps-adminmenu:client:setPed", Player.PlayerData.source, ped)
end)

View File

@ -0,0 +1,151 @@
local function getVehicles(cid)
local result = MySQL.query.await(
'SELECT vehicle, plate, fuel, engine, body FROM player_vehicles WHERE citizenid = ?', { cid })
local vehicles = {}
for k, v in pairs(result) do
local vehicleData = QBCore.Shared.Vehicles[v.vehicle]
if vehicleData then
vehicles[#vehicles + 1] = {
id = k,
cid = cid,
label = vehicleData.name,
brand = vehicleData.brand,
model = vehicleData.model,
plate = v.plate,
fuel = v.fuel,
engine = v.engine,
body = v.body
}
end
end
return vehicles
end
local function getPlayers()
local players = {}
local GetPlayers = QBCore.Functions.GetQBPlayers()
for k, v in pairs(GetPlayers) do
local playerData = v.PlayerData
local vehicles = getVehicles(playerData.citizenid)
players[#players + 1] = {
id = k,
name = playerData.charinfo.firstname .. ' ' .. playerData.charinfo.lastname,
cid = playerData.citizenid,
license = QBCore.Functions.GetIdentifier(k, 'license'),
discord = QBCore.Functions.GetIdentifier(k, 'discord'),
steam = QBCore.Functions.GetIdentifier(k, 'steam'),
job = playerData.job.label,
grade = playerData.job.grade.level,
dob = playerData.charinfo.birthdate,
cash = playerData.money.cash,
bank = playerData.money.bank,
phone = playerData.charinfo.phone,
vehicles = vehicles
}
end
table.sort(players, function(a, b) return a.id < b.id end)
return players
end
lib.callback.register('ps-adminmenu:callback:GetPlayers', function(source)
return getPlayers()
end)
-- Set Job
RegisterNetEvent('ps-adminmenu:server:SetJob', function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local src = source
local playerId, Job, Grade = selectedData["Player"].value, selectedData["Job"].value, selectedData["Grade"].value
local Player = QBCore.Functions.GetPlayer(playerId)
local name = Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname
local jobInfo = QBCore.Shared.Jobs[Job]
local grade = jobInfo["grades"][selectedData["Grade"].value]
if not jobInfo then
TriggerClientEvent('QBCore:Notify', source, "Ikke et gyldigt job", 'error')
return
end
if not grade then
TriggerClientEvent('QBCore:Notify', source, "Ikke et gyldigt job", 'error')
return
end
Player.Functions.SetJob(tostring(Job), tonumber(Grade))
if Config.RenewedPhone then
exports['qb-phone']:hireUser(tostring(Job), Player.PlayerData.citizenid, tonumber(Grade))
end
QBCore.Functions.Notify(src, locale("jobset", name, Job, Grade), 'success', 5000)
end)
-- Set Gang
RegisterNetEvent('ps-adminmenu:server:SetGang', function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local src = source
local playerId, Gang, Grade = selectedData["Player"].value, selectedData["Gang"].value, selectedData["Grade"].value
local Player = QBCore.Functions.GetPlayer(playerId)
local name = Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname
local GangInfo = QBCore.Shared.Gangs[Gang]
local grade = GangInfo["grades"][selectedData["Grade"].value]
if not GangInfo then
TriggerClientEvent('QBCore:Notify', source, "Ikke en gyldig bande", 'error')
return
end
if not grade then
TriggerClientEvent('QBCore:Notify', source, "Ikke en gyldig bande", 'error')
return
end
Player.Functions.SetGang(tostring(Gang), tonumber(Grade))
QBCore.Functions.Notify(src, locale("gangset", name, Gang, Grade), 'success', 5000)
end)
-- Set Perms
RegisterNetEvent("ps-adminmenu:server:SetPerms", function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local src = source
local rank = selectedData["Permissions"].value
local targetId = selectedData["Player"].value
local tPlayer = QBCore.Functions.GetPlayer(tonumber(targetId))
if not tPlayer then
QBCore.Functions.Notify(src, locale("not_online"), "error", 5000)
return
end
local name = tPlayer.PlayerData.charinfo.firstname .. ' ' .. tPlayer.PlayerData.charinfo.lastname
QBCore.Functions.AddPermission(tPlayer.PlayerData.source, tostring(rank))
QBCore.Functions.Notify(tPlayer.PlayerData.source, locale("player_perms", name, rank), 'success', 5000)
end)
-- Remove Stress
RegisterNetEvent("ps-adminmenu:server:RemoveStress", function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local src = source
local targetId = selectedData['Player (Optional)'] and tonumber(selectedData['Player (Optional)'].value) or src
local tPlayer = QBCore.Functions.GetPlayer(tonumber(targetId))
if not tPlayer then
QBCore.Functions.Notify(src, locale("not_online"), "error", 5000)
return
end
TriggerClientEvent('ps-adminmenu:client:removeStress', targetId)
QBCore.Functions.Notify(tPlayer.PlayerData.source, locale("removed_stress_player"), 'success', 5000)
end)

View File

@ -0,0 +1,32 @@
local commandsTable, addedCommands = {}, {}
local blacklistCommands = {
"sv_", "adhesive_", "citizen_", "con_", "endpoint_", "fileserver", "load_server",
"mysql_connection", "net_tcp", "netPort", "netlib", "onesync", "onesync_",
"rateLimiter_", "svgui", "web_base", "temp_", "txAdmin", "txa",
}
local function isCommandBlacklisted(commandName)
for _, bcommand in pairs(blacklistCommands) do
if string.match(commandName, '^' .. bcommand) then
return true
end
end
return false
end
lib.callback.register('ps-adminmenu:callback:GetCommands', function()
if not CheckPerms(Config.ShowCommandsPerms) then return {} end
local allCommands = GetRegisteredCommands()
for _, command in ipairs(allCommands) do
if not isCommandBlacklisted(command.name) and not addedCommands[command.name] then
commandsTable[#commandsTable + 1] = {
name = '/' .. command.name
}
addedCommands[command.name] = true -- prevent duplicates
end
end
return commandsTable
end)

View File

@ -0,0 +1,45 @@
local resources = {}
lib.callback.register('ps-adminmenu:callback:GetResources', function(source)
local totalResources = GetNumResources()
resources = {}
for i = 0, totalResources - 1 do
local resourceName = GetResourceByFindIndex(i)
local author = GetResourceMetadata(resourceName, "author")
local version = GetResourceMetadata(resourceName, "version")
local description = GetResourceMetadata(resourceName, "description")
local resourceState = GetResourceState(resourceName)
resources[#resources + 1] = {
name = resourceName,
author = author,
version = version,
description = description,
resourceState = resourceState,
}
end
return resources
end)
lib.callback.register('ps-adminmenu:callback:ChangeResourceState', function(source, data, perms)
if not CheckPerms(Config.ResourcePerms) then return end
if data.state == "start" then
StartResource(data.name)
print("Started " .. data.name)
elseif data.state == "stop" then
StopResource(data.name)
print("Stopped " .. data.name)
elseif data.state == "restart" then
StopResource(data.name)
Wait(200)
StartResource(data.name)
print("Restarted " .. data.name)
end
return resources
end)

View File

@ -0,0 +1,42 @@
local spectating = {}
RegisterNetEvent('ps-adminmenu:server:SpectateTarget', function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local player = selectedData["Player"].value
local type = "1"
if player == source then return QBCore.Functions.Notify(source, locale("cant_spectate_yourself"), 'error', 7500) end
if spectating[source] then type = "0" end
TriggerEvent('ps-adminmenu:spectate', player, type == "1", source, data.perms)
CheckRoutingbucket(source, player)
end)
AddEventHandler('ps-adminmenu:spectate', function(target, on, source, perms)
local tPed = GetPlayerPed(target)
local data = {}
data.perms = perms
if DoesEntityExist(tPed) then
if not on then
TriggerClientEvent('ps-adminmenu:cancelSpectate', source)
spectating[source] = false
FreezeEntityPosition(GetPlayerPed(source), false)
TriggerClientEvent('ps-adminmenu:client:toggleNames', source, data)
elseif on then
TriggerClientEvent('ps-adminmenu:requestSpectate', source, NetworkGetNetworkIdFromEntity(tPed), target,
GetPlayerName(target))
spectating[source] = true
TriggerClientEvent('ps-adminmenu:client:toggleNames', source, data)
end
end
end)
RegisterNetEvent('ps-adminmenu:spectate:teleport', function(target)
local source = source
local ped = GetPlayerPed(target)
if DoesEntityExist(ped) then
local targetCoords = GetEntityCoords(ped)
SetEntityCoords(GetPlayerPed(source), targetCoords.x, targetCoords.y, targetCoords.z - 10)
FreezeEntityPosition(GetPlayerPed(source), true)
end
end)

View File

@ -0,0 +1,28 @@
-- Teleport To Player
RegisterNetEvent('ps-adminmenu:server:TeleportToPlayer', function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local src = source
local player = selectedData["Player"].value
local targetPed = GetPlayerPed(player)
local coords = GetEntityCoords(targetPed)
CheckRoutingbucket(src, player)
TriggerClientEvent('ps-adminmenu:client:TeleportToPlayer', src, coords)
end)
-- Bring Player
RegisterNetEvent('ps-adminmenu:server:BringPlayer', function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local src = source
local targetPed = selectedData["Player"].value
local admin = GetPlayerPed(src)
local coords = GetEntityCoords(admin)
local target = GetPlayerPed(targetPed)
CheckRoutingbucket(targetPed, src)
SetEntityCoords(target, coords)
end)

View File

@ -0,0 +1,50 @@
-- Freeze Player
local frozen = false
RegisterNetEvent('ps-adminmenu:server:FreezePlayer', function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local src = source
local target = selectedData["Player"].value
local ped = GetPlayerPed(target)
local Player = QBCore.Functions.GetPlayer(target)
if not frozen then
frozen = true
FreezeEntityPosition(ped, true)
QBCore.Functions.Notify(src,
locale("Frozen",
Player.PlayerData.charinfo.firstname ..
" " .. Player.PlayerData.charinfo.lastname .. " | " .. Player.PlayerData.citizenid), 'Success', 7500)
else
frozen = false
FreezeEntityPosition(ped, false)
QBCore.Functions.Notify(src,
locale("deFrozen",
Player.PlayerData.charinfo.firstname ..
" " .. Player.PlayerData.charinfo.lastname .. " | " .. Player.PlayerData.citizenid), 'Success', 7500)
end
if Player == nil then return QBCore.Functions.Notify(src, locale("not_online"), 'error', 7500) end
end)
-- Drunk Player
RegisterNetEvent('ps-adminmenu:server:DrunkPlayer', function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local src = source
local target = selectedData["Player"].value
local targetPed = GetPlayerPed(target)
local Player = QBCore.Functions.GetPlayer(target)
if not Player then
return QBCore.Functions.Notify(src, locale("not_online"), 'error', 7500)
end
TriggerClientEvent('ps-adminmenu:client:InitiateDrunkEffect', target)
QBCore.Functions.Notify(src,
locale("playerdrunk",
Player.PlayerData.charinfo.firstname ..
" " .. Player.PlayerData.charinfo.lastname .. " | " .. Player.PlayerData.citizenid), 'Success', 7500)
end)

View File

@ -0,0 +1,77 @@
local function noPerms(source)
QBCore.Functions.Notify(source, "You are not Admin or God.", 'error')
end
--- @param perms string
function CheckPerms(perms)
local hasPerms = QBCore.Functions.HasPermission(source, perms)
if not hasPerms then
return noPerms(source)
end
return hasPerms
end
function CheckDataFromKey(key)
local actions = Config.Actions[key]
if actions then
local data = nil
if actions.event then
data = actions
end
if actions.dropdown then
for _, v in pairs(actions.dropdown) do
if v.event then
local new = v
new.perms = actions.perms
data = new
break
end
end
end
return data
end
local playerActions = Config.PlayerActions[key]
if playerActions then
return playerActions
end
local otherActions = Config.OtherActions[key]
if otherActions then
return otherActions
end
end
---@param plate string
---@return boolean
function CheckAlreadyPlate(plate)
local vPlate = QBCore.Shared.Trim(plate)
local result = MySQL.single.await("SELECT plate FROM player_vehicles WHERE plate = ?", { vPlate })
if result and result.plate then return true end
return false
end
lib.callback.register('ps-adminmenu:callback:CheckPerms', function(source, perms)
return CheckPerms(perms)
end)
lib.callback.register('ps-adminmenu:callback:CheckAlreadyPlate', function(_, vPlate)
return CheckAlreadyPlate(vPlate)
end)
--- @param source number
--- @param target number
function CheckRoutingbucket(source, target)
local sourceBucket = GetPlayerRoutingBucket(source)
local targetBucket = GetPlayerRoutingBucket(target)
if sourceBucket == targetBucket then return end
SetPlayerRoutingBucket(source, targetBucket)
QBCore.Functions.Notify(source, locale("bucket_set", targetBucket), 'error', 7500)
end

View File

@ -0,0 +1,152 @@
-- Admin Car
RegisterNetEvent('ps-adminmenu:server:SaveCar', function(mods, vehicle, _, plate)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
local result = MySQL.query.await('SELECT plate FROM player_vehicles WHERE plate = ?', { plate })
if result[1] == nil then
MySQL.insert(
'INSERT INTO player_vehicles (license, citizenid, vehicle, hash, mods, plate, state) VALUES (?, ?, ?, ?, ?, ?, ?)',
{
Player.PlayerData.license,
Player.PlayerData.citizenid,
vehicle.model,
vehicle.hash,
json.encode(mods),
plate,
0
})
TriggerClientEvent('QBCore:Notify', src, locale("veh_owner"), 'success', 5000)
else
TriggerClientEvent('QBCore:Notify', src, locale("u_veh_owner"), 'error', 3000)
end
end)
-- Give Car
RegisterNetEvent("ps-adminmenu:server:givecar", function(data, selectedData)
local src = source
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then
QBCore.Functions.Notify(src, locale("no_perms"), "error", 5000)
return
end
local vehmodel = selectedData['Vehicle'].value
local vehicleData = lib.callback.await("ps-adminmenu:client:getvehData", src, vehmodel)
if not next(vehicleData) then
return
end
local tsrc = selectedData['Player'].value
local plate = selectedData['Plate (Optional)'] and selectedData['Plate (Optional)'].value or vehicleData.plate
local garage = selectedData['Garage (Optional)'] and selectedData['Garage (Optional)'].value or Config.DefaultGarage
local Player = QBCore.Functions.GetPlayer(tsrc)
if plate and #plate < 1 then
plate = vehicleData.plate
end
if garage and #garage < 1 then
garage = Config.DefaultGarage
end
if plate:len() > 8 then
QBCore.Functions.Notify(src, locale("plate_max"), "error", 5000)
return
end
if not Player then
QBCore.Functions.Notify(src, locale("not_online"), "error", 5000)
return
end
if CheckAlreadyPlate(plate) then
QBCore.Functions.Notify(src, locale("givecar.error.plates_alreadyused", plate:upper()), "error", 5000)
return
end
MySQL.insert(
'INSERT INTO player_vehicles (license, citizenid, vehicle, hash, mods, plate, garage, state) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
{
Player.PlayerData.license,
Player.PlayerData.citizenid,
vehmodel,
joaat(vehmodel),
json.encode(vehicleData),
plate,
garage,
1
})
QBCore.Functions.Notify(src,
locale("givecar.success.source", QBCore.Shared.Vehicles[vehmodel].name,
("%s %s"):format(Player.PlayerData.charinfo.firstname, Player.PlayerData.charinfo.lastname)), "success", 5000)
QBCore.Functions.Notify(Player.PlayerData.source, locale("givecar.success.target", plate:upper(), garage), "success",
5000)
end)
-- Give Car
RegisterNetEvent("ps-adminmenu:server:SetVehicleState", function(data, selectedData)
local src = source
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then
QBCore.Functions.Notify(src, locale("no_perms"), "error", 5000)
return
end
local plate = string.upper(selectedData['Plate'].value)
local state = tonumber(selectedData['State'].value)
if plate:len() > 8 then
QBCore.Functions.Notify(src, locale("plate_max"), "error", 5000)
return
end
if not CheckAlreadyPlate(plate) then
QBCore.Functions.Notify(src, locale("plate_doesnt_exist"), "error", 5000)
return
end
MySQL.update('UPDATE player_vehicles SET state = ?, depotprice = ? WHERE plate = ?', { state, 0, plate })
QBCore.Functions.Notify(src, locale("state_changed"), "success", 5000)
end)
-- Change Plate
RegisterNetEvent('ps-adminmenu:server:ChangePlate', function(newPlate, currentPlate)
local newPlate = newPlate:upper()
if Config.Inventory == 'ox_inventory' then
exports.ox_inventory:UpdateVehicle(currentPlate, newPlate)
end
MySQL.Sync.execute('UPDATE player_vehicles SET plate = ? WHERE plate = ?', { newPlate, currentPlate })
MySQL.Sync.execute('UPDATE trunkitems SET plate = ? WHERE plate = ?', { newPlate, currentPlate })
MySQL.Sync.execute('UPDATE gloveboxitems SET plate = ? WHERE plate = ?', { newPlate, currentPlate })
end)
lib.callback.register('ps-adminmenu:server:GetVehicleByPlate', function(source, plate)
local result = MySQL.query.await('SELECT vehicle FROM player_vehicles WHERE plate = ?', { plate })
local veh = result[1] and result[1].vehicle or {}
return veh
end)
-- Fix Vehicle for player
RegisterNetEvent('ps-adminmenu:server:FixVehFor', function(data, selectedData)
local data = CheckDataFromKey(data)
if not data or not CheckPerms(data.perms) then return end
local src = source
local playerId = selectedData['Player'].value
local Player = QBCore.Functions.GetPlayer(tonumber(playerId))
if Player then
local name = Player.PlayerData.charinfo.firstname .. " " .. Player.PlayerData.charinfo.lastname
TriggerClientEvent('iens:repaira', Player.PlayerData.source)
TriggerClientEvent('vehiclemod:client:fixEverything', Player.PlayerData.source)
QBCore.Functions.Notify(src, locale("veh_fixed", name), 'success', 7500)
else
TriggerClientEvent('QBCore:Notify', src, locale("not_online"), "error")
end
end)

View File

@ -0,0 +1,776 @@
Config = Config or {}
Config.Fuel = "qb-fuel" -- "ps-fuel", "LegacyFuel"
Config.ResourcePerms = 'admin' -- permission to control resource(start stop restart)
Config.ShowCommandsPerms = 'admin' -- permission to show all commands
Config.RenewedPhone = false -- if you use qb-phone from renewed. (multijob)
-- Key Bindings
Config.Keybindings = true
Config.AdminKey = "PageDown"
Config.NoclipKey = "PageUp"
-- Give Car
Config.DefaultGarage = "pillboxgarage"
Config.Actions = {
["admin_car"] = {
label = "Admin Car",
type = "client",
event = "ps-adminmenu:client:Admincar",
perms = "mod",
},
["ban_player"] = {
label = "Ban Player",
perms = "mod",
dropdown = {
{ label = "Player", option = "dropdown", data = "players" },
{ label = "Reason", option = "text" },
{
label = "Duration",
option = "dropdown",
data = {
{ label = "Permanent", value = "2147483647" },
{ label = "10 Minutes", value = "600" },
{ label = "30 Minutes", value = "1800" },
{ label = "1 Hour", value = "3600" },
{ label = "6 Hours", value = "21600" },
{ label = "12 Hours", value = "43200" },
{ label = "1 Day", value = "86400" },
{ label = "3 Days", value = "259200" },
{ label = "1 Week", value = "604800" },
{ label = "3 Week", value = "1814400" },
},
},
{ label = "Confirm", option = "button", type = "server", event = "ps-adminmenu:server:BanPlayer" },
},
},
["server_info"] = {
label = "Send server-wide message",
perms = "mod",
dropdown = {
{ label = "Message", option = "text" },
{
label = "Duration",
option = "dropdown",
data = {
{ label = "20 sekunder", value = "20" },
{ label = "15 sekunder", value = "15" },
{ label = "10 sekunder", value = "10" },
{ label = "5 sekunder", value = "5" },
},
},
{ label = "Send besked", option = "button", type = "server", event = "ps-adminmenu:server:ServerMessage" },
},
},
["player_info"] = {
label = "Send message to player",
perms = "mod",
dropdown = {
{ label = "Message", option = "text" },
{ label = "Player", option = "dropdown", data = "players" },
{
label = "Duration",
option = "dropdown",
data = {
{ label = "20 sekunder", value = "20" },
{ label = "15 sekunder", value = "15" },
{ label = "10 sekunder", value = "10" },
{ label = "5 sekunder", value = "5" },
},
},
{ label = "Send besked", option = "button", type = "server", event = "ps-adminmenu:server:ServerMessage" },
},
},
["bring_player"] = {
label = "Bring Player",
perms = "mod",
dropdown = {
{ label = "Player", option = "dropdown", data = "players" },
{ label = "Confirm", option = "button", type = "server", event = "ps-adminmenu:server:BringPlayer" },
},
},
["change_weather"] = {
label = "Change Weather",
perms = "mod",
dropdown = {
{
label = "Weather",
option = "dropdown",
data = {
{ label = "Extrasunny", value = "Extrasunny" },
{ label = "Clear", value = "Clear" },
{ label = "Neutral", value = "Neutral" },
{ label = "Smog", value = "Smog" },
{ label = "Foggy", value = "Foggy" },
{ label = "Overcast", value = "Overcast" },
{ label = "Clouds", value = "Clouds" },
{ label = "Clearing", value = "Clearing" },
{ label = "Rain", value = "Rain" },
{ label = "Thunder", value = "Thunder" },
{ label = "Snow", value = "Snow" },
{ label = "Blizzard", value = "Blizzard" },
{ label = "Snowlight", value = "Snowlight" },
{ label = "Xmas", value = "Xmas" },
{ label = "Halloween", value = "Halloween" },
},
},
{ label = "Confirm", option = "button", type = "client", event = "ps-adminmenu:client:ChangeWeather" },
},
},
["change_time"] = {
label = "Change Time",
perms = "mod",
dropdown = {
{
label = "Time Events",
option = "dropdown",
data = {
{ label = "Sunrise", value = "06" },
{ label = "Morning", value = "09" },
{ label = "Noon", value = "12" },
{ label = "Sunset", value = "21" },
{ label = "Evening", value = "22" },
{ label = "Night", value = "24" },
},
},
{ label = "Confirm", option = "button", type = "client", event = "ps-adminmenu:client:ChangeTime" },
},
},
["change_plate"] = {
label = "Change Plate",
perms = "mod",
dropdown = {
{ label = "Plate", option = "text" },
{ label = "Confirm", option = "button", type = "client", event = "ps-adminmenu:client:ChangePlate" },
},
},
["clear_inventory"] = {
label = "Clear Inventory",
perms = "mod",
dropdown = {
{ label = "Player", option = "dropdown", data = "players" },
{ label = "Confirm", option = "button", type = "server", event = "ps-adminmenu:server:ClearInventory" },
},
},
["clear_inventory_offline"] = {
label = "Clear Inventory Offline",
perms = "mod",
dropdown = {
{ label = "Citizen ID", option = "text", data = "players" },
{ label = "Confirm", option = "button", type = "server", event = "ps-adminmenu:server:ClearInventoryOffline" },
},
},
["clothing_menu"] = {
label = "Give Clothing Menu",
perms = "mod",
dropdown = {
{ label = "Player", option = "dropdown", data = "players" },
{ label = "Confirm", option = "button", type = "server", event = "ps-adminmenu:server:ClothingMenu" },
},
},
["character_menu"] = {
label = "Force character creation",
perms = "mod",
dropdown = {
{ label = "Player", option = "dropdown", data = "players" },
},
{ label = "Force character creation", option = "button", type = "server", event = "ps-adminmenu:server:CharacterCreation"},
},
["set_ped"] = {
label = "Set Ped",
perms = "mod",
dropdown = {
{ label = "Player", option = "dropdown", data = "players" },
{ label = "Ped Models", option = "dropdown", data = "pedlist" },
},
{ label = "Set Ped", option = "button", type = "server", event = "ps-adminmenu:server:setPed" },
},
["copy_coords"] = {
label = "Copy Coords",
perms = "mod",
dropdown = {
{
label = "Copy Coords",
option = "dropdown",
data = {
{ label = "Copy Vector2", value = "vector2" },
{ label = "Copy Vector3", value = "vector3" },
{ label = "Copy Vector4", value = "vector4" },
{ label = "Copy Heading", value = "heading" },
},
},
{ label = "Copy to Clipboard", option = "button", type = "client", event = "ps-adminmenu:client:copyToClipboard"},
},
},
["delete_vehicle"] = {
label = "Delete Vehicle",
type = "command",
event = "dv",
perms = "mod",
},
["freeze_player"] = {
label = "Freeze Player",
perms = "mod",
dropdown = {
{ label = "Player", option = "dropdown", data = "players" },
{ label = "Confirm", option = "button", type = "server", event = "ps-adminmenu:server:FreezePlayer" },
},
},
["drunk_player"] = {
label = "Make Player Drunk",
perms = "mod",
dropdown = {
{ label = "Player", option = "dropdown", data = "players" },
{ label = "Confirm", option = "button", type = "server", event = "ps-adminmenu:server:DrunkPlayer" },
},
},
["remove_stress"] = {
label = "Remove Stress",
perms = "mod",
dropdown = {
{ label = "Player (Optional)", option = "dropdown", data = "players" },
{ label = "Confirm", option = "button", type = "server", event = "ps-adminmenu:server:RemoveStress" },
},
},
["set_ammo"] = {
label = "Set Ammo",
perms = "admin",
dropdown = {
{ label = "Ammo Ammount", option = "text" },
{ label = "Confirm", option = "button", type = "client", event = "ps-adminmenu:client:SetAmmo" },
},
},
-- ["nui_focus"] = {
-- label = "Give NUI Focus",
-- perms = "mod",
-- dropdown = {
-- { label = "Player", option = "dropdown", data = "players" },
-- { label = "Confirm", option = "button", type = "client", event = "" },
-- },
-- },
["god_mode"] = {
label = "God Mode",
type = "client",
event = "ps-adminmenu:client:ToggleGodmode",
perms = "mod",
},
["give_car"] = {
label = "Give Car",
perms = "admin",
dropdown = {
{ label = "Vehicle", option = "dropdown", data = "vehicles" },
{ label = "Player", option = "dropdown", data = "players" },
{ label = "Plate (Optional)", option = "text" },
{ label = "Garage (Optional)", option = "text" },
{ label = "Confirm", option = "button", type = "server", event = "ps-adminmenu:server:givecar" },
}
},
["invisible"] = {
label = "Invisible",
type = "client",
event = "ps-adminmenu:client:ToggleInvisible",
perms = "mod",
},
["blackout"] = {
label = "Toggle Blackout",
type = "server",
event = "ps-adminmenu:server:ToggleBlackout",
perms = "mod",
},
["toggle_duty"] = {
label = "Toggle Duty",
type = "server",
event = "QBCore:ToggleDuty",
perms = "mod",
},
["toggle_laser"] = {
label = "Toggle Laser",
type = "client",
event = "ps-adminmenu:client:ToggleLaser",
perms = "mod",
},
["set_perms"] = {
label = "Set Perms",
perms = "admin",
dropdown = {
{ label = "Player", option = "dropdown", data = "players" },
{
label = "Permissions",
option = "dropdown",
data = {
{ label = "Mod", value = "mod" },
{ label = "Admin", value = "admin" },
{ label = "God", value = "god" },
},
},
{ label = "Confirm", option = "button", type = "server", event = "ps-adminmenu:server:SetPerms" },
},
},
["set_bucket"] = {
label = "Set Routing Bucket",
perms = "mod",
dropdown = {
{ label = "Player", option = "dropdown", data = "players" },
{ label = "Bucket", option = "text" },
{ label = "Confirm", option = "button", type = "server", event = "ps-adminmenu:server:SetBucket" },
},
},
["get_bucket"] = {
label = "Get Routing Bucket",
perms = "mod",
dropdown = {
{ label = "Player", option = "dropdown", data = "players" },
{ label = "Confirm", option = "button", type = "server", event = "ps-adminmenu:server:GetBucket" },
},
},
["mute_player"] = {
label = "Mute Player",
perms = "mod",
dropdown = {
{ label = "Player", option = "dropdown", data = "players" },
{ label = "Confirm", option = "button", type = "client", event = "ps-adminmenu:client:MutePlayer" },
},
},
["noclip"] = {
label = "Noclip",
type = "client",
event = "ps-adminmenu:client:ToggleNoClip",
perms = "mod",
},
["debug"] = {
label = "Debug",
type = "client",
event = "hud:enabledebug",
perms = "mod",
},
["open_inventory"] = {
label = "Open Inventory",
perms = "mod",
dropdown = {
{ label = "Player", option = "dropdown", data = "players" },
{ label = "Confirm", option = "button", type = "client", event = "ps-adminmenu:client:openInventory" },
},
},
["open_stash"] = {
label = "Open Stash",
perms = "mod",
dropdown = {
{ label = "Stash", option = "text" },
{ label = "Confirm", option = "button", type = "client", event = "ps-adminmenu:client:openStash" },
},
},
["open_trunk"] = {
label = "Open Trunk",
perms = "mod",
dropdown = {
{ label = "Plate", option = "text" },
{ label = "Confirm", option = "button", type = "client", event = "ps-adminmenu:client:openTrunk" },
},
},
["change_vehicle_state"] = {
label = "Set Vehicle Garage State",
perms = "mod",
dropdown = {
{ label = "Plate", option = "text" },
{
label = "State",
option = "dropdown",
data = {
{ label = "In", value = "1" },
{ label = "Out", value = "0" },
},
},
{ label = "Confirm", option = "button", type = "server", event = "ps-adminmenu:server:SetVehicleState" },
},
},
["revive_all"] = {
label = "Revive All",
type = "server",
event = "ps-adminmenu:server:ReviveAll",
perms = "mod",
},
["revive_player"] = {
label = "Revive Player",
perms = "mod",
dropdown = {
{ label = "Player", option = "dropdown", data = "players" },
{ label = "Confirm", option = "button", type = "server", event = "ps-adminmenu:server:Revive" },
},
},
["revive_radius"] = {
label = "Revive Radius",
type = "server",
event = "ps-adminmenu:server:ReviveRadius",
perms = "mod",
},
["refuel_vehicle"] = {
label = "Refuel Vehicle",
type = "client",
event = "ps-adminmenu:client:RefuelVehicle",
perms = "mod",
},
["set_job"] = {
label = "Set Job",
perms = "mod",
dropdown = {
{ label = "Player", option = "dropdown", data = "players" },
{ label = "Job", option = "dropdown", data = "jobs" },
{ label = "Grade", option = "text", data = "grades" },
{ label = "Confirm", option = "button", type = "server", event = "ps-adminmenu:server:SetJob" },
},
},
["set_gang"] = {
label = "Set Gang",
perms = "mod",
dropdown = {
{ label = "Player", option = "dropdown", data = "players" },
{ label = "Gang", option = "dropdown", data = "gangs" },
{ label = "Grade", option = "text", data = "grades" },
{ label = "Confirm", option = "button", type = "server", event = "ps-adminmenu:server:SetGang" },
},
},
["give_money"] = {
label = "Give Money",
perms = "admin",
dropdown = {
{ label = "Player", option = "dropdown", data = "players" },
{ label = "Amount", option = "text" },
{
label = "Type",
option = "dropdown",
data = {
{ label = "Cash", value = "cash" },
{ label = "Bank", value = "bank" },
{ label = "Crypto", value = "crypto" },
},
},
{ label = "Confirm", option = "button", type = "server", event = "ps-adminmenu:server:GiveMoney" },
},
},
["give_money_all"] = {
label = "Give Money to All",
perms = "admin",
dropdown = {
{ label = "Amount", option = "text" },
{
label = "Type",
option = "dropdown",
data = {
{ label = "Cash", value = "cash" },
{ label = "Bank", value = "bank" },
{ label = "Crypto", value = "crypto" },
},
},
{ label = "Confirm", option = "button", type = "server", event = "ps-adminmenu:server:GiveMoneyAll" },
},
},
["remove_money"] = {
label = "Remove Money",
perms = "admin",
dropdown = {
{ label = "Player", option = "dropdown", data = "players" },
{ label = "Amount", option = "text" },
{
label = "Type",
option = "dropdown",
data = {
{ label = "Cash", value = "cash" },
{ label = "Bank", value = "bank" },
},
},
{ label = "Confirm", option = "button", type = "server", event = "ps-adminmenu:server:TakeMoney" },
},
},
["give_item"] = {
label = "Give Item",
perms = "mod",
dropdown = {
{ label = "Player", option = "dropdown", data = "players" },
{ label = "Item", option = "dropdown", data = "items" },
{ label = "Amount", option = "text" },
{ label = "Confirm", option = "button", type = "server", event = "ps-adminmenu:server:GiveItem" },
},
},
["give_item_all"] = {
label = "Give Item to All",
perms = "mod",
dropdown = {
{ label = "Item", option = "dropdown", data = "items" },
{ label = "Amount", option = "text" },
{ label = "Confirm", option = "button", type = "server", event = "ps-adminmenu:server:GiveItemAll" },
},
},
["spawn_vehicle"] = {
label = "Spawn Vehicle",
perms = "mod",
dropdown = {
{ label = "Vehicle", option = "dropdown", data = "vehicles" },
{ label = "Confirm", option = "button", type = "client", event = "ps-adminmenu:client:SpawnVehicle" },
},
},
["fix_vehicle"] = {
label = "Fix Vehicle",
type = "command",
event = "fix",
perms = "mod",
},
["fix_vehicle_for"] = {
label = "Fix Vehicle for player",
perms = "mod",
dropdown = {
{ label = "Player", option = "dropdown", data = "players" },
{ label = "Confirm", option = "button", type = "server", event = "ps-adminmenu:server:FixVehFor" },
},
},
["spectate_player"] = {
label = "Spectate Player",
perms = "mod",
dropdown = {
{ label = "Player", option = "dropdown", data = "players" },
{ label = "Confirm", option = "button", type = "server", event = "ps-adminmenu:server:SpectateTarget" },
},
},
["telport_to_player"] = {
label = "Teleport to Player",
perms = "mod",
dropdown = {
{ label = "Player", option = "dropdown", data = "players" },
{ label = "Confirm", option = "button", type = "server", event = "ps-adminmenu:server:TeleportToPlayer" },
},
},
["telport_to_coords"] = {
label = "Teleport to Coords",
perms = "mod",
dropdown = {
{ label = "Coords", option = "text" },
{ label = "Confirm", option = "button", type = "client", event = "ps-adminmenu:client:TeleportToCoords" },
},
},
["teleport_to_location"] = {
label = "Teleport to Location",
perms = "mod",
dropdown = {
{ label = "Location", option = "dropdown", data = "locations" },
{ label = "Confirm", option = "button", type = "client", event = "ps-adminmenu:client:TeleportToLocation" },
},
},
["teleport_to_marker"] = {
label = "Teleport to Marker",
type = "command",
event = "tpm",
perms = "mod",
},
["teleport_back"] = {
label = "Teleport Back",
type = "client",
event = "ps-adminmenu:client:TeleportBack",
perms = "mod",
},
["vehicle_dev"] = {
label = "Vehicle Dev Menu",
type = "client",
event = "ps-adminmenu:client:ToggleVehDevMenu",
perms = "mod",
},
["toggle_coords"] = {
label = "Toggle Coords",
type = "client",
event = "ps-adminmenu:client:ToggleCoords",
perms = "mod",
},
["toggle_blips"] = {
label = "Toggle Blips",
type = "client",
event = "ps-adminmenu:client:toggleBlips",
perms = "mod",
},
["toggle_names"] = {
label = "Toggle Names",
type = "client",
event = "ps-adminmenu:client:toggleNames",
perms = "mod",
},
["toggle_cuffs"] = {
label = "Toggle Cuffs",
perms = "mod",
dropdown = {
{ label = "Player", option = "dropdown", data = "players" },
{ label = "Confirm", option = "button", type = "server", event = "ps-adminmenu:server:CuffPlayer" },
},
},
["max_mods"] = {
label = "Max Vehicle Mods",
type = "client",
event = "ps-adminmenu:client:maxmodVehicle",
perms = "mod",
},
["warn_player"] = {
label = "Warn Player",
perms = "mod",
dropdown = {
{ label = "Player", option = "dropdown", data = "players" },
{ label = "Reason", option = "text" },
{ label = "Confirm", option = "button", type = "server", event = "ps-adminmenu:server:WarnPlayer" },
},
},
["infinite_ammo"] = {
label = "Infinite Ammo",
type = "client",
event = "ps-adminmenu:client:setInfiniteAmmo",
perms = "mod",
},
["kick_player"] = {
label = "Kick Player",
perms = "mod",
dropdown = {
{ label = "Player", option = "dropdown", data = "players" },
{ label = "Reason", option = "text" },
{ label = "Confirm", option = "button", type = "server", event = "ps-adminmenu:server:KickPlayer" },
},
},
["play_sound"] = {
label = "Play Sound",
perms = "mod",
dropdown = {
{ label = "Player", option = "dropdown", data = "players" },
{
label = "Sound",
option = "dropdown",
data = {
{ label = "Alert", value = "alert" },
{ label = "Cuff", value = "cuff" },
{ label = "Air Wrench", value = "airwrench" },
},
},
{ label = "Play Sound", option = "button", type = "client", event = "ps-adminmenu:client:PlaySound" },
},
},
}
Config.PlayerActions = {
["teleportToPlayer"] = {
label = "Teleport to Player",
type = "server",
event = "ps-adminmenu:server:TeleportToPlayer",
perms = "mod",
},
["bringPlayer"] = {
label = "Bring Player",
type = "server",
event = "ps-adminmenu:server:BringPlayer",
perms = "mod",
},
["revivePlayer"] = {
label = "Revive Player",
event = "ps-adminmenu:server:Revive",
perms = "mod",
type = "server"
},
["spawnPersonalVehicle"] = {
label = "Spawn Personal Vehicle",
event = "ps-adminmenu:client:SpawnPersonalVehicle",
perms = "mod",
type = "client"
},
["banPlayer"] = {
label = "Ban Player",
event = "ps-adminmenu:server:BanPlayer",
perms = "mod",
type = "server"
},
["kickPlayer"] = {
label = "Kick Player",
event = "ps-adminmenu:server:KickPlayer",
perms = "mod",
type = "server"
}
}
Config.OtherActions = {
["toggleDevmode"] = {
type = "client",
event = "ps-adminmenu:client:ToggleDev",
perms = "admin",
label = "Toggle Devmode"
}
}
AddEventHandler("onResourceStart", function()
Wait(100)
if GetResourceState('ox_inventory') == 'started' then
Config.Inventory = 'ox_inventory'
elseif GetResourceState('ps-inventory') == 'started' then
Config.Inventory = 'ps-inventory'
elseif GetResourceState('lj-inventory') == 'started' then
Config.Inventory = 'lj-inventory'
elseif GetResourceState('qb-inventory') == 'started' then
Config.Inventory = 'qb-inventory'
end
end)

View File

@ -0,0 +1,4 @@
/node_modules/
.DS_Store

View File

@ -0,0 +1,6 @@
{
"tabWidth": 4,
"useTabs": true,
"semi": false,
"singleQuote": true
}

View File

@ -0,0 +1,44 @@
# Svelte + TS + Vite
This template should help get you started developing with Svelte and TypeScript in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode).
## Need an official Svelte framework?
Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more.
## Technical considerations
**Why use this over SvelteKit?**
- It brings its own routing solution which might not be preferable for some users.
- It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app.
`vite dev` and `vite build` wouldn't work in a SvelteKit environment, for example.
This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project.
Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate.
**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?**
Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information.
**Why enable `allowJs` in the TS template?**
While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds: not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant.
**Why is HMR not preserving my local component state?**
HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr).
If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR.
```ts
// store.ts
// An extremely simple external store
import { writable } from 'svelte/store'
export default writable(0)
```

View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- FontAwesome Icons Import -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css">
<!-- Material Icons Import -->
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<!-- Material Design Icons Import -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font/css/materialdesignicons.min.css">
<!-- Line Awesome Icons Import -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/line-awesome/dist/line-awesome/css/line-awesome.min.css">
<!-- Boostrap Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.min.css">
<title>ps-adminmenu</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,24 @@
{
"name": "would_you_rather",
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.json"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^2.4.3",
"@tsconfig/svelte": "^5.0.0",
"autoprefixer": "^10.4.14",
"postcss": "^8.4.27",
"svelte": "^4.1.2",
"svelte-check": "^3.4.6",
"svelte-preprocess": "^5.0.4",
"tailwindcss": "^3.3.3",
"tslib": "^2.6.1",
"typescript": "^5.1.6",
"vite": "^4.4.9"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
import tailwind from 'tailwindcss';
import autoprefixer from 'autoprefixer';
import tailwindConfig from './tailwind.config.cjs';
export default {
plugins: [tailwind(tailwindConfig), autoprefixer],
}

View File

@ -0,0 +1,38 @@
<script lang="ts">
import VisibilityProvider from '@providers/VisibilityProvider.svelte'
import { BROWSER_MODE, RESOURCE_NAME } from '@store/stores'
import DebugBrowser from '@providers/DebugBrowser.svelte'
import AlwaysListener from '@providers/AlwaysListener.svelte'
import Main from './layout/Main.svelte'
import VehicleDev from '@components/VehicleDev.svelte'
import { VEHICLE_DEV } from '@store/vehicle_dev'
import ToggleCoords from '@components/ToggleCoords.svelte'
import { TOGGLE_COORDS } from '@store/togglecoords'
import { ENTITY_INFO } from '@store/entityInfo'
import EntityInformation from '@components/EntityInformation.svelte'
$RESOURCE_NAME = 'ps-adminmenu'
</script>
<VisibilityProvider>
<Main />
</VisibilityProvider>
{#if $VEHICLE_DEV?.show}
<VehicleDev />
{/if}
{#if $TOGGLE_COORDS?.show}
<ToggleCoords />
{/if}
{#if $ENTITY_INFO?.show}
<EntityInformation />
{/if}
<AlwaysListener />
{#if $BROWSER_MODE}
<DebugBrowser />
<div class="absolute w-screen h-screen bg-neutral-800" />
{/if}

View File

@ -0,0 +1,160 @@
<script>
import { GANG_DATA, ITEM_DATA, JOB_DATA, LOCATION_DATA, VEHICLE_DATA, PED_LIST } from "@store/data"
import { PLAYER } from "@store/players"
import { SendNUI } from "@utils/SendNUI"
import { onMount } from "svelte"
import { slide } from "svelte/transition"
export let action
export let label_title
export let data
export let selectedData;
let search = ""
let searchInputFocused = false;
let DataDropdownActive = false;
function selectData(label, value) {
search = label;
DataDropdownActive = false;
selectedData({
label: label,
value: value,
id: label_title
});
}
function handleInputFocus() {
searchInputFocused = true;
DataDropdownActive = true;
search = ""
}
function handleInputBlur() {
if (!searchInputFocused) {
DataDropdownActive = false;
}
searchInputFocused = false;
}
async function GetPlayers() {
const players = await SendNUI("getPlayers");
PLAYER.set(players);
}
onMount(() => {
if (data === "players") {
GetPlayers();
}
})
</script>
<div class="w-[22vh] flex flex-col bg-secondary rounded-[0.5vh] border-[0.1vh] border-primary">
<div class="w-full h-[3.8vh] px-[1vh] flex justify-between items-center">
<input
type="text"
placeholder={label_title}
on:focus={handleInputFocus}
on:blur={handleInputBlur}
bind:value={search}
class="h-full w-[90%] bg-transparent"
/>
<i class="fas fa-angle-{DataDropdownActive ? "down" : "right"} text-[1.2vh]"></i>
</div>
{#if DataDropdownActive}
<button
class="w-full rounded-b-[0.5vh] flex flex-col max-h-[15vh] overflow-y-auto border-t border-primary scroll-visible"
on:mouseenter={() => { searchInputFocused = true }}
on:blur={() => { searchInputFocused = false }}
transition:slide={{ duration: 150 }}
>
{#if data === "players"}
{#each $PLAYER.filter(i => i.name.toLowerCase().includes(search.toLowerCase())) as i}
<button
class="w-full p-[0.5vh] flex justify-start text-start px-[1vh] gap-[0.5vh] hover:bg-tertiary"
on:click={() =>
selectData(i.name, i.id)
}
>
<p>{i.name}</p>
<p>({i.id})</p>
</button>
{/each}
{:else if data === "vehicles"}
{#each $VEHICLE_DATA.filter(i => i.label.toLowerCase().includes(search.toLowerCase()) || i.value.toLowerCase().includes(search.toLowerCase())) as i}
<button
class="w-full p-[0.5vh] flex justify-start text-start px-[1vh] gap-[0.5vh] hover:bg-tertiary"
on:click={() => selectData(i.label, i.value)}
>
<p>{i.label}</p>
<p>({i.value})</p>
</button>
{/each}
{:else if data === "items"}
{#each $ITEM_DATA.filter(i => i.label.toLowerCase().includes(search.toLowerCase()) || i.value.toLowerCase().includes(search.toLowerCase())) as i}
<button
class="w-full p-[0.5vh] flex justify-start text-start px-[1vh] gap-[0.5vh] hover:bg-tertiary"
on:click={() => selectData(i.label, i.value)}
>
<p>{i.label}</p>
<p>({i.value})</p>
</button>
{/each}
{:else if data === "jobs"}
{#each $JOB_DATA.filter(i => i.label.toLowerCase().includes(search.toLowerCase()) || i.value.toLowerCase().includes(search.toLowerCase())) as i}
<button
class="w-full p-[0.5vh] flex justify-start text-start px-[1vh] gap-[0.5vh] hover:bg-tertiary"
on:click={() => selectData(i.label, i.value)}
>
<p>{i.label}</p>
<p>({i.value})</p>
</button>
{/each}
{:else if data === "gangs"}
{#each $GANG_DATA.filter(i => i.label.toLowerCase().includes(search.toLowerCase()) || i.value.toLowerCase().includes(search.toLowerCase())) as i}
<button
class="w-full p-[0.5vh] flex justify-start text-start px-[1vh] gap-[0.5vh] hover:bg-tertiary"
on:click={() => selectData(i.label, i.value)}
>
<p>{i.label}</p>
<p>({i.value})</p>
</button>
{/each}
{:else if data === "locations"}
{#each $LOCATION_DATA.filter(i => i.label.toLowerCase().includes(search.toLowerCase()) || i.value.toLowerCase().includes(search.toLowerCase())) as i}
<button
class="w-full p-[0.5vh] flex justify-start text-start px-[1vh] gap-[0.5vh] hover:bg-tertiary"
on:click={() => selectData(i.label, i.value)}
>
<p>{i.label}</p>
<p>({i.value})</p>
</button>
{/each}
{:else if data === "pedlist"}
{#each $PED_LIST.filter(i => i.label.toLowerCase().includes(search.toLowerCase()) || i.value.toLowerCase().includes(search.toLowerCase())) as i}
<button
class="w-full p-[0.5vh] flex justify-start text-start px-[1vh] gap-[0.5vh] hover:bg-tertiary"
on:click={() => selectData(i.label, i.value)}
>
<p>{i.label}</p>
<p>({i.value})</p>
</button>
{/each}
{:else}
{#each data.filter(i => i.label.toLowerCase().includes(search.toLowerCase()) || i.value.toLowerCase().includes(search.toLowerCase())) as i}
<button
class="w-full p-[0.5vh] flex justify-start text-start px-[1vh] gap-[0.5vh] hover:bg-tertiary"
on:click={() => selectData(i.label, i.value)}
>
<p>{i.label}</p>
<!-- <p>({i.value})</p> -->
</button>
{/each}
{/if}
</button>
{/if}
</div>

View File

@ -0,0 +1,164 @@
<script>
import {createEventDispatcher} from 'svelte';
const dispatch = createEventDispatcher();
export let dropdownValues, label, selectedValue, id="array-dd", insideLabel = "";
let isOpen = false;
function toggleDropdown() {
isOpen = !isOpen;
const selectWrapper = document.getElementById('select');
if(isOpen) {
selectWrapper.style.borderBottomLeftRadius = '0';
selectWrapper.style.borderBottomRightRadius = '0';
} else {
selectWrapper.style.borderBottomLeftRadius = '0.2vw';
selectWrapper.style.borderBottomRightRadius = '0.2vw';
}
}
function selectDropdownValue(value) {
selectedValue = value;
dispatch('selected-dropdown', value);
isOpen = false;
}
</script>
<div class="dropdown" id={id}>
{#if label.trim() !== ''}
<label for="dd">{label}: </label>
{/if}
<div class="dropdown-wrapper">
<div class="select-wrapper" id="select" on:click={toggleDropdown}>
{#if selectedValue.trim() !== ''}
<div class="select-wrapper-selected-value">
{#if insideLabel.trim() !== ""}
<p class="inside-label">{insideLabel}</p>
{/if}
<p class="selected-value-text">{selectedValue}</p>
</div>
{:else}
<div></div>
{/if}
<i class="fas fa-chevron-down dropdown-chevron"></i>
</div>
{#if isOpen}
<div class="options-wrapper">
{#if dropdownValues.length < 1}
<div class="no-items-found">
No items found
</div>
{:else}
{#each dropdownValues as ddValue}
<div class="option-child" on:click={() => {selectDropdownValue(ddValue)}}>
<p>
{ddValue}
{#if selectedValue === ddValue}
<i class="fas fa-check icon"></i>
{/if}
</p>
</div>
{/each}
{/if}
</div>
{/if}
</div>
</div>
<style>
.dropdown > label {
margin: 0 0.2vw;
color: var(--light-text);
}
.dropdown-wrapper {
display: flex;
flex-direction: column;
min-width: 6vw;
width: fit-content;
/* padding: 0 0.2vw; */
background: linear-gradient(0deg, #242424, #242424), linear-gradient(0deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.1));
border: 1px solid rgba(255, 255, 255, 0.1);
color: var(--app-name);
border-radius: 0.2vw;
}
.select-wrapper {
width: auto;
height: 1.7vw;
display: flex;
flex-direction: row;
justify-content: space-between;
padding-left: 0.2vw;
padding-right: 0.3vw;
cursor: pointer;
}
.select-wrapper-selected-value {
font-size: 0.7vw;
padding: 0.25vw;
margin-right: 0.2vw;
display: flex;
flex-direction: row;
}
.select-wrapper-selected-value > .inside-label {
color: var(--less-light-border-color);
margin-right: 0.3vw;
}
.select-wrapper-selected-value > .selected-value-text {
padding-top: 0.05vw;
}
.dropdown-chevron {
text-align: center;
font-size: 0.5vw;
margin-top: 0.6vw;
color: var(--less-light-border-color);
}
.options-wrapper {
width: 100%;
}
.options-wrapper > .no-items-found {
font-size: 0.7vw;
padding: 0.17vw 0.6vw 0.17vw 0.6vw;
}
.option-child {
cursor: pointer;
padding: 0.17vw 0.8vw 0.17vw 0.45vw;
min-height: 1.7vw;
border-radius: 0.2vw;
font-size: 0.7vw;
}
.option-child > p {
display: flex;
flex-direction: row;
}
.option-child > p > .icon {
font-size: 0.65vw;
padding-top: 0.35vw;
margin-left: 0.5vw;
}
.option-child:hover {
background-color: var(--black-two-opaque-color);
}
/** ARRAY DROPDOWN - end **/
</style>

View File

@ -0,0 +1,21 @@
<script>
import { ENTITY_INFO } from "@store/entityInfo"
import { fly } from "svelte/transition"
</script>
<div class="w-screen h-screen flex items-center">
<div class="w-[25vh] bg-primary flex flex-col gap-[2vh] rounded-[0.5vh] p-[2vh] ml-[2vh] font-medium" transition:fly={{ x: -100 }}>
<div class="h-[2vh] w-full flex items-center gap-[1vh] text-[1.5vh]">
<i class="fas fa-code"></i>
<p>Entity Information</p>
</div>
<div>
<p>Model: {$ENTITY_INFO?.name}</p>
<p>Hash: {$ENTITY_INFO?.hash}</p>
<br>
<p>C - Copy Information</p>
<p>E - Delete Entity</p>
<p>ESC - Close</p>
</div>
</div>
</div>

View File

@ -0,0 +1,26 @@
<script>
import { MENU_WIDE } from '@store/stores'
export let title
export let hasSearch = false
export let hasLargeMenu = false
export let onSearchInput = null
export let search = null
</script>
<p class="my-[2vh] font-medium text-[2vh]">{title}</p>
{#if hasSearch}
<div
class="w-full h-[4.5vh] rounded-[0.5vh] flex items-center justify-center gap-[1vh] bg-tertiary"
>
<i class="fas fa-magnifying-glass text-[1.5vh]" />
<input
on:input={onSearchInput}
bind:value={search}
type="text"
placeholder="Search"
class="h-full px-[1vh] bg-transparent text-[1.7vh] {hasLargeMenu ? $MENU_WIDE ? 'w-[94%]' : 'w-[80%]' : 'w-[80%]'}"
/>
</div>
{/if}

View File

@ -0,0 +1,5 @@
<div class="fixed top-0 left-0 bottom-0 right-0 flex items-center justify-center bg-black bg-opacity-75">
<div class=" bg-tertiary rounded-[0.5vh] flex flex-col px-[2vh] py-[1.5vh] gap-[0.8vh]">
<slot></slot>
</div>
</div>

View File

@ -0,0 +1,7 @@
<div class="w-full h-full flex justify-center items-center opacity-50">
<div
class="inline-block h-[10rem] w-[10rem] animate-spin rounded-full border-8 border-solid border-secondary border-r-tertiary align-[-0.125em] motion-reduce:animate-[spin_1.5s_linear_infinite]"
role="status">
</div>
</div>

View File

@ -0,0 +1,19 @@
<script>
import { TOGGLE_COORDS } from "@store/togglecoords"
import { fly } from "svelte/transition"
</script>
<div class="w-screen h-screen flex items-center">
<div class="w-[25vh] bg-primary flex flex-col gap-[2vh] rounded-[0.5vh] p-[2vh] ml-[2vh] font-medium" transition:fly={{ x: -100 }}>
<div class="h-[2vh] w-full flex items-center gap-[1vh] text-[1.5vh]">
<i class="fas fa-code"></i>
<p>Coords Information</p>
</div>
<div>
<p>X: {$TOGGLE_COORDS?.x}</p>
<p>Y: {$TOGGLE_COORDS?.y}</p>
<p>Z: {$TOGGLE_COORDS?.z}</p>
<p>Heading: {$TOGGLE_COORDS?.heading}</p>
</div>
</div>
</div>

View File

@ -0,0 +1,22 @@
<script>
import { VEHICLE_DEV } from "@store/vehicle_dev"
import { fly } from "svelte/transition"
</script>
<div class="w-screen h-screen flex items-center">
<div class="w-[25vh] bg-primary flex flex-col gap-[2vh] rounded-[0.5vh] p-[2vh] ml-[2vh] font-medium" transition:fly={{ x: -100 }}>
<div class="h-[2vh] w-full flex items-center gap-[1vh] text-[1.5vh]">
<i class="fas fa-code"></i>
<p>Vehicle Information</p>
</div>
<div>
<p>Model: {$VEHICLE_DEV?.name}</p>
<p>Hash: {$VEHICLE_DEV?.model}</p>
<p>NetID: {$VEHICLE_DEV?.netID}</p>
<p>Plate: {$VEHICLE_DEV?.plate}</p>
<p>Fuel: {$VEHICLE_DEV?.fuel}</p>
<p>Engine: {$VEHICLE_DEV?.engine_health}</p>
<p>Body: {$VEHICLE_DEV?.body_health}</p>
</div>
</div>
</div>

View File

@ -0,0 +1,30 @@
<script>
import { fly } from 'svelte/transition'
import { MENU_WIDE, ACTIVE_PAGE } from '@store/stores'
import Sidebar from './Sidebar/Sidebar.svelte'
import Actions from '@pages/Actions/Actions.svelte'
import Server from '@pages/Server/Server.svelte'
import StaffChat from '@pages/Chat/Chat.svelte'
import Players from '@pages/Players/Players.svelte'
import Commands from '@pages/Commands/Commands.svelte'
</script>
<div
class="h-[85vh] flex rounded-[0.5vh] bg-primary {!$MENU_WIDE ? 'w-[40vh] mr-[5vh] ' : 'w-[106vh]'}" transition:fly={{ x: 100 }}
>
<Sidebar />
<div class="h-full flex {!$MENU_WIDE ? 'w-[33vh]' : 'w-[99vh]'}">
{#if $ACTIVE_PAGE == 'Actions'}
<Actions />
{:else if $ACTIVE_PAGE == 'Server'}
<Server />
{:else if $ACTIVE_PAGE == 'Staffchat'}
<StaffChat />
{:else if $ACTIVE_PAGE == 'Players'}
<Players />
{:else if $ACTIVE_PAGE == 'Commands'}
<Commands />
{/if}
</div>
</div>

View File

@ -0,0 +1,43 @@
<script>
import { ACTIVE_PAGE } from '@store/stores'
export let icon
export let value
export let tooltiptext
</script>
<button
title={tooltiptext}
class="w-[4vh] h-[4vh] rounded-[0.5vh] hover:bg-tertiary {$ACTIVE_PAGE == value ? 'bg-tertiary' : ''}
relative
before:content-[attr(data-tip)]
before:absolute
before:-right-3 before:top-1/2
before:w-max before:max-w-xs
before:px-[1vh]
before:py-[0.5vh]
before:-translate-x-full before:-translate-y-1/2
before:bg-tertiary before:text-white
before:rounded-md before:opacity-0
before:translate-all
after:absolute
after:-right-3 after:top-1/2
after:-translate-x-0 after:-translate-y-1/2
after:h-0 after:w-0
after:border-t-transparent
after:border-l-transparent
after:border-b-transparent
after:border-r-tertiary
after:opacity-0
after:transition-all
hover:before:opacity-100 hover:after:opacity-100
"
data-tip={tooltiptext}
on:click={() => {
ACTIVE_PAGE.set(value)
}}
>
<i class={icon} />
</button>

View File

@ -0,0 +1,74 @@
<script>
import { DEV_MODE, MENU_WIDE } from '@store/stores'
import Button from './Components/Button.svelte'
import { SendNUI } from '@utils/SendNUI'
let navigation = [
{ value: 'Staffchat', icon: 'fas fa-message' },
{ value: 'Players', icon: 'fas fa-users' },
{ value: 'Server', icon: 'fas fa-server' },
{ value: 'Commands', icon: 'fas fa-slash' },
{ value: 'Actions', icon: 'fas fa-wand-magic-sparkles' },
]
</script>
<div
class="w-[7vh] h-full flex flex-col gap-y-[1vh] items-center py-[1.4vh] border-r-[0.2vh] border-tertiary"
>
<div class="mb-auto">
<button
class="w-[4vh] h-[4vh] rounded-[0.5vh] hover:bg-tertiary"
on:click={() => MENU_WIDE.update((wide) => !wide)}
>
<i
class="fas"
class:fa-angle-right={$MENU_WIDE}
class:fa-angle-left={!$MENU_WIDE}
/>
</button>
</div>
{#each navigation as nav}
<Button tooltiptext={nav.value} icon={nav.icon} value={nav.value} />
{/each}
<button
class="w-[4vh] h-[4vh] rounded-[0.5vh] hover:bg-tertiary {$DEV_MODE
? 'text-accent'
: ''}
relative
before:content-[attr(data-tip)]
before:absolute
before:-right-3 before:top-1/2
before:w-max before:max-w-xs
before:px-[1vh]
before:py-[0.5vh]
before:-translate-x-full before:-translate-y-1/2
before:bg-tertiary before:text-white
before:rounded-md before:opacity-0
before:translate-all
after:absolute
after:-right-3 after:top-1/2
after:-translate-x-0 after:-translate-y-1/2
after:h-0 after:w-0
after:border-t-transparent
after:border-l-transparent
after:border-b-transparent
after:border-r-tertiary
after:opacity-0
after:transition-all
hover:before:opacity-100 hover:after:opacity-100
"
data-tip="Dev Mode"
on:click={() => {
DEV_MODE.update((wide) => !wide)
SendNUI('clickButton', {
data: 'toggleDevmode',
})
}}
>
<i class="fas fa-code"></i>
</button>
</div>

View File

@ -0,0 +1,8 @@
import App from './App.svelte'
import './styles.css'
const app = new App({
target: document.getElementById('app')
})
export default app

View File

@ -0,0 +1,43 @@
<script lang="ts">
import { MENU_WIDE, searchActions } from '@store/stores'
import { ACTION, ALL_ACTIONS } from '@store/actions'
import Header from '@components/Header.svelte'
import Tabs from './components/Tabs.svelte'
import Button from './components/Button.svelte'
import Dropdown from './components/Dropdown.svelte'
import ButtonState from '@pages/Server/components/ButtonState.svelte'
</script>
<div class="h-full w-[99vh] px-[2vh]">
<Header
title={'Actions'}
hasSearch={true}
hasLargeMenu={true}
onSearchInput={event => $searchActions = event.target.value}
search={$searchActions}
/>
<Tabs />
<div class="w-full h-[77%] flex flex-col gap-[1vh] mt-[1vh] overflow-auto scroll-visble">
{#if $ACTION}
{#each Object.entries($ACTION)
.filter(([actionKey, actionValue]) => {
if ($ALL_ACTIONS) {
return actionValue.label.toLowerCase().includes($searchActions.toLowerCase());
} else {
return localStorage.getItem(`favorite-${actionKey}`) === 'true';
}
})
.sort(([aKey, aValue], [bKey, bValue]) =>
aValue.label.localeCompare(bValue.label)
) as [actionKey, actionValue]}
{#if actionValue.dropdown}
<Dropdown data={actionValue} id={actionKey} />
{:else}
<Button data={actionValue} id={actionKey} />
{/if}
{/each}
{/if}
</div>
</div>

View File

@ -0,0 +1,27 @@
<script>
import { SendNUI } from '@utils/SendNUI'
import Favorite from './Favorite.svelte'
import { onMount } from 'svelte'
export let data
export let id
onMount(() => {
// console.log("button", data)
})
</script>
<button
class="min-h-[4.5vh] w-full flex items-center px-[1.5vh] rounded-[0.5vh] bg-tertiary hover:bg-opacity-90"
on:click={() => {
// console.log(data.event)
SendNUI('clickButton', {
data: id,
})
}}
>
<div class="flex items-center gap-[1vh]">
<Favorite data={id} />
<p>{data.label}</p>
</div>
</button>

View File

@ -0,0 +1,83 @@
<script>
import DropdownComponent from '@components/DropdownComponent.svelte'
import { SendNUI } from '@utils/SendNUI'
import { slide } from 'svelte/transition'
import Autofill from '@components/Autofill.svelte'
import Favorite from './Favorite.svelte'
import Input from './Input.svelte'
export let data
export let id
let dropdownActive
let selectedDataArray = {}
function SelectData(selectedData) {
// console.log('selected', selectedData)
selectedDataArray[selectedData.id] = selectedData
}
function sendData(event, type) {
if (event) {
data.event = event
data.type = type
}
// console.log('data', data)
// console.log('event', data.event)
// console.log('type', data.type)
SendNUI('clickButton', {
data: id,
selectedData: selectedDataArray,
})
// console.log(selectedDataArray)
}
</script>
<div
class=" bg-tertiary rounded-[0.5vh] {dropdownActive
? ''
: 'hover:bg-opacity-90'}"
>
<button
class="w-full h-[4.5vh] flex items-center justify-between px-[1.5vh]"
on:click={() => (dropdownActive = !dropdownActive)}
>
<div class="flex items-center gap-[1vh]">
<Favorite data={id} />
<p>{data.label}</p>
</div>
<i class="fas fa-angle-{dropdownActive ? 'down' : 'right'}" />
</button>
{#if dropdownActive}
<div
class="w-full rounded-b-[1vh] p-[1.5vh] flex flex-col gap-[1vh] justify-start items-start"
transition:slide={{ duration: 150 }}
>
{#if data.dropdown}
{#each data.dropdown as i}
{#if i.option === 'text'}
<Input data={i} selectedData={SelectData} />
{:else if i.option === 'dropdown'}
<Autofill
action={i}
label_title={i.label}
data={i.data}
selectedData={SelectData}
/>
{:else if i.option === 'button'}
<button
class="h-[3.8vh] px-[1.5vh] rounded-[0.5vh] bg-secondary hover:bg-opacity-90 border-[0.1vh] border-primary"
on:click={() => {
sendData(i.event, i.type)
}}
>
<p>{i.label}</p>
</button>
{/if}
{/each}
{/if}
</div>
{/if}
</div>

View File

@ -0,0 +1,24 @@
<script>
import { onMount } from "svelte"
export let data
let favorite = localStorage.getItem(`favorite-${data}`) === 'true';
const toggleFavorite = () => {
event.stopPropagation();
favorite = !favorite;
localStorage.setItem(`favorite-${data}`, favorite);
// console.log(data, "is now", favorite ? "favorited" : "unfavorited");
};
onMount(() => {
// console.log(data)
})
</script>
<button
class="{favorite ? 'fas' : 'far'} fa-star"
on:click={toggleFavorite}
>
</button>

View File

@ -0,0 +1,30 @@
<script>
export let data
export let selectedData
function selectData(label, value) {
selectedData({
label: label,
value: value,
id: label
});
}
let input = ""
</script>
<div class="w-[22vh] flex flex-col bg-secondary rounded-[0.5vh] border-[0.1vh] border-primary">
<div class="w-full h-[3.8vh] pl-[1vh] flex justify-between">
<input
type="text"
placeholder={data.label}
bind:value={input}
on:input={e => input = e.target.value}
on:blur={() => selectData(data.label, input)}
on:click={() => selectData(data.label, input)}
class="h-full w-[90%] bg-transparent"
/>
</div>
</div>

View File

@ -0,0 +1,24 @@
<script>
import { ALL_ACTIONS } from '@store/actions'
</script>
<div class="mt-[1vh] w-full h-[4.5vh] flex gap-[1vh] font-medium">
<button
class="w-full h-full hover:bg-tertiary rounded-[0.5vh] border-[0.2vh] border-tertiary {$ALL_ACTIONS ? 'bg-tertiary' : ' '}"
on:click={() => {
ALL_ACTIONS.set(true)
// console.log($ALL_ACTIONS)
}}
>
All Actions
</button>
<button
class="w-full h-full hover:bg-tertiary rounded-[0.5vh] border-[0.2vh] border-tertiary {!$ALL_ACTIONS ? 'bg-tertiary' : ' '}"
on:click={() => {
ALL_ACTIONS.set(false)
// console.log($ALL_ACTIONS)
}}
>
Favorites
</button>
</div>

View File

@ -0,0 +1,15 @@
<script>
import Header from "@components/Header.svelte"
import SendInput from "./components/SendInput.svelte"
import RecieveInput from "./components/RecieveInput.svelte"
let search = "";
</script>
<div class="h-full w-full px-[2vh] pb-[2vh] flex flex-col ">
<Header title={"Staff Chat"}/>
<div id='chatList' class="w-full h-[84%] overflow-auto">
<RecieveInput />
</div>
<SendInput />
</div>

View File

@ -0,0 +1,40 @@
<script>
import { MENU_WIDE } from '@store/stores'
import { Message, Messages } from '@store/staffchat'
import { PLAYER, PLAYER_DATA } from "@store/players";
import { timeAgo } from "@utils/timeAgo"
import { SendNUI } from '@utils/SendNUI'
import { onMount } from 'svelte'
function refreshMsg() {
SendNUI("GetMessages");
}
onMount(() => {
const RefreshMsgInterval = setInterval(() => {
refreshMsg()
}, 1000)
return () => clearInterval(RefreshMsgInterval)
})
</script>
<div>
<div>
{#if $Message && $Messages}
{#each $Message as message}
<div class="w-full flex flex-col text-[1.3vh] text-gray-400 {$PLAYER && $PLAYER_DATA.cid === message.citizenid ? "items-end" : "items-start"}">
<div class="w-fit flex justify-between items-center text-gray-400 font-medium -mb-[0.5vh]">
<p class="text-[1.2vh]">{message.fullname}</p>
</div>
<div class="w-fit max-w-[85%] mt-[0.5vh] p-[1vh] break-words text-start rounded-lg {$PLAYER && $PLAYER_DATA.cid === message.citizenid ? "bg-accent" : "bg-tertiary"}">
<p>{message.message}</p>
</div>
<p class="text-[1vh] ml-[0.5vh]">{timeAgo(Number(message.time))}</p>
</div>
{/each}
{/if}
</div>
</div>

View File

@ -0,0 +1,47 @@
<script>
import { MENU_WIDE } from '@store/stores'
import { SendNUI } from '@utils/SendNUI'
let message = "";
function sendMessage() {
if (!message.trim()) return;
SendNUI("SendMessage", {
message: message,
});
// console.log("Message sent", message)
message = "";
setTimeout(() => {
scrollToBottom();
}, 100);
}
function scrollToBottom() {
let chatList = document.getElementById("chatList");
chatList.scroll({
top: chatList.scrollHeight,
behavior: 'auto'
});
}
</script>
<div
class="mt-auto w-full h-[4.5vh] rounded-[0.5vh] flex items-center justify-center gap-[1vh] bg-tertiary"
>
<input
type="text"
placeholder="Your message here"
on:keydown={(e) => {
if (e.key === "Enter") {
sendMessage();
}
}}
bind:value={message}
class="h-full px-[1vh] bg-transparent text-[1.7vh] {$MENU_WIDE ? 'w-[94%]' : 'w-[80%]'}"
/>
<button
class="h-full w-[5vh] rounded-r-[0.5vh] hover:bg-secondary"
on:click={sendMessage}
>
<i class="fas fa-paper-plane text-[1.5vh]" />
</button>
</div>

View File

@ -0,0 +1,37 @@
<script>
import { MENU_WIDE } from '@store/stores'
import { COMMANDS } from '@store/server'
import Header from '@components/Header.svelte'
import CommandsCard from './components/CommandsCard.svelte'
let search = ''
let SortedCommands = $COMMANDS ? $COMMANDS.slice().sort((a, b) => a.name.localeCompare(b.name)) : []
</script>
<div class="h-full w-[33vh] px-[2vh]">
<Header
title={'Commands'}
hasSearch={true}
onSearchInput={event => (search = event.target.value)}
/>
<div class="w-full h-[84%] flex flex-col gap-[1vh] mt-[1vh] overflow-auto">
{#if $COMMANDS}
{#if $COMMANDS && $COMMANDS.filter((commands) => commands.name.toLowerCase().includes(search.toLowerCase())).length === 0}
<div class="text-tertiary text-center text-[1.7vh] font-medium mt-[1vh]">No Commands Found.</div>
{:else}
<small class="font-medium">Total Commands: {SortedCommands.length}</small>
{#each SortedCommands.filter((commands) => commands.name.toLowerCase().includes(search.toLowerCase())) as commands}
<CommandsCard label={commands.name} />
{/each}
{/if}
{/if}
</div>
</div>
{#if $MENU_WIDE}
<div class="h-full w-[66vh] border-l-[0.2vh] border-tertiary px-[2vh]">
<Header title={'Dashboard'} />
</div>
{/if}

View File

@ -0,0 +1,10 @@
<script>
export let label
</script>
<button class="w-full flex justify-between rounded-[0.5vh] bg-tertiary items-center">
<div class="flex items-center p-[2vh]">
<i class="fas fa-angle-right mr-[1vh]"></i>
<p class="text-[1.5vh]">{label ? label : ''}</p>
</div>
</button>

View File

@ -0,0 +1,486 @@
<script lang="ts">
import { MENU_WIDE } from '@store/stores'
import { PLAYER, PLAYER_VEHICLES, SELECTED_PLAYER } from '@store/players'
import Header from '@components/Header.svelte'
import Button from './components/Button.svelte'
import { onMount } from 'svelte'
import { SendNUI } from '@utils/SendNUI'
import Spinner from '@components/Spinner.svelte'
import Autofill from '@components/Autofill.svelte'
import Modal from '@components/Modal.svelte'
import Input from '@pages/Actions/components/Input.svelte'
let search = ''
let loading = false
let banPlayer = false
let kickPlayer = false
onMount(async () => {
loading = true
const players = await SendNUI('getPlayers')
PLAYER.set(players)
loading = false
})
let selectedDataArray = {}
function SelectData(selectedData) {
// console.log('selected', selectedData)
selectedDataArray[selectedData.id] = selectedData
// console.log('selectedDataArray', selectedDataArray)
}
let banData = [
{ label: 'Permanent', value: '2147483647' },
{ label: '10 Minutes', value: '600' },
{ label: '30 Minutes', value: '1800' },
{ label: '1 Hour', value: '3600' },
{ label: '6 Hours', value: '21600' },
{ label: '12 Hours', value: '43200' },
{ label: '1 Day', value: '86400' },
{ label: '3 Days', value: '259200' },
{ label: '1 Week', value: '604800' },
{ label: '3 Weeks', value: '1814400' },
]
</script>
<div class="h-full w-[33vh] px-[2vh]">
<Header
title={'Players'}
hasSearch={true}
onSearchInput={(event) => (search = event.target.value)}
/>
<div class="w-full h-[84%] flex flex-col gap-[1vh] mt-[1vh] overflow-auto">
{#if loading}
<Spinner />
{:else if $PLAYER}
{#if $PLAYER && $PLAYER.filter((player) => player.name
.toLowerCase()
.includes(search.toLowerCase())).length === 0}
<div
class="text-tertiary text-center text-[1.7vh] font-medium mt-[1vh]"
>
No Player Found.
</div>
{:else}
{#each $PLAYER.filter((player) => player.name
.toLowerCase()
.includes(search.toLowerCase())) as player}
<Button {player} />
{/each}
{/if}
{/if}
</div>
</div>
{#if $MENU_WIDE}
<div class="h-full w-[66vh] border-l-[0.2vh] border-tertiary p-[2vh]">
{#if !$SELECTED_PLAYER}
<div
class="h-full w-full flex flex-col items-center justify-center"
>
<div class="text-4xl text-tertiary">No Player Selected.</div>
</div>
{:else}
<p class="text-[2vh] font-medium">
ID: {$SELECTED_PLAYER.id} - {$SELECTED_PLAYER.name}
</p>
<div class="w-full h-[96.5%] pt-[2vh] flex flex-col gap-[1vh]">
<p class="font-medium text-[1.7vh]">Quick Actions</p>
<div class="w-full bg-tertiary flex rounded-[0.5vh]">
<button
title="Kick Player"
class="h-[4.5vh] w-full rounded-l-[0.5vh] hover:bg-secondary
relative
before:content-[attr(data-tip)]
before:absolute
before:px-3 before:py-2
before:left-1/2 before:-top-3
before:w-max before:max-w-xs
before:-translate-x-1/2 before:-translate-y-full
before:bg-tertiary before:text-white
before:rounded-md before:opacity-0
before:translate-all
after:absolute
after:left-1/2 after:-top-3
after:h-0 after:w-0
after:-translate-x-1/2 after:border-8
after:border-t-tertiary
after:border-l-transparent
after:border-b-transparent
after:border-r-transparent
after:opacity-0
after:transition-all
hover:before:opacity-100 hover:after:opacity-100
"
data-tip="Kick Player"
on:click={() => (kickPlayer = true)}
>
<i class="fas fa-user-minus"></i>
</button>
<button
title="Ban Player"
class="h-[4.5vh] w-full hover:bg-secondary
relative
before:content-[attr(data-tip)]
before:absolute
before:px-3 before:py-2
before:left-1/2 before:-top-3
before:w-max before:max-w-xs
before:-translate-x-1/2 before:-translate-y-full
before:bg-tertiary before:text-white
before:rounded-md before:opacity-0
before:translate-all
after:absolute
after:left-1/2 after:-top-3
after:h-0 after:w-0
after:-translate-x-1/2 after:border-8
after:border-t-tertiary
after:border-l-transparent
after:border-b-transparent
after:border-r-transparent
after:opacity-0
after:transition-all
hover:before:opacity-100 hover:after:opacity-100
"
data-tip="Ban Player"
on:click={() => (banPlayer = true)}
>
<i class="fas fa-ban"></i>
</button>
<button
title="Teleport To Player"
class="h-[4.5vh] w-full hover:bg-secondary
relative
before:content-[attr(data-tip)]
before:absolute
before:px-3 before:py-2
before:left-1/2 before:-top-3
before:w-max before:max-w-xs
before:-translate-x-1/2 before:-translate-y-full
before:bg-tertiary before:text-white
before:rounded-md before:opacity-0
before:translate-all
after:absolute
after:left-1/2 after:-top-3
after:h-0 after:w-0
after:-translate-x-1/2 after:border-8
after:border-t-tertiary
after:border-l-transparent
after:border-b-transparent
after:border-r-transparent
after:opacity-0
after:transition-all
hover:before:opacity-100 hover:after:opacity-100
"
data-tip="Teleport To Player"
on:click={() =>
SendNUI('clickButton', {
data: 'teleportToPlayer',
selectedData: {
['Player']: {
value: $SELECTED_PLAYER.id,
},
},
})}
>
<i class="fas fa-person-walking-arrow-right"></i>
</button>
<button
title="Bring Player"
class="h-[4.5vh] w-full hover:bg-secondary
relative
before:content-[attr(data-tip)]
before:absolute
before:px-3 before:py-2
before:left-1/2 before:-top-3
before:w-max before:max-w-xs
before:-translate-x-1/2 before:-translate-y-full
before:bg-tertiary before:text-white
before:rounded-md before:opacity-0
before:translate-all
after:absolute
after:left-1/2 after:-top-3
after:h-0 after:w-0
after:-translate-x-1/2 after:border-8
after:border-t-tertiary
after:border-l-transparent
after:border-b-transparent
after:border-r-transparent
after:opacity-0
after:transition-all
hover:before:opacity-100 hover:after:opacity-100
"
data-tip="Bring Player"
on:click={() =>
SendNUI('clickButton', {
data: 'bringPlayer',
selectedData: {
['Player']: {
value: $SELECTED_PLAYER.id,
},
},
})}
>
<i class="fas fa-person-walking-arrow-loop-left"></i>
</button>
<button
title="Revive Player"
class="h-[4.5vh] w-full hover:bg-secondary
relative
before:content-[attr(data-tip)]
before:absolute
before:px-3 before:py-2
before:left-1/2 before:-top-3
before:w-max before:max-w-xs
before:-translate-x-1/2 before:-translate-y-full
before:bg-tertiary before:text-white
before:rounded-md before:opacity-0
before:translate-all
after:absolute
after:left-1/2 after:-top-3
after:h-0 after:w-0
after:-translate-x-1/2 after:border-8
after:border-t-tertiary
after:border-l-transparent
after:border-b-transparent
after:border-r-transparent
after:opacity-0
after:transition-all
hover:before:opacity-100 hover:after:opacity-100
"
data-tip="Revive Player"
on:click={() =>
SendNUI('clickButton', {
data: 'revivePlayer',
selectedData: {
['Player']: {
value: $SELECTED_PLAYER.id,
},
},
})}
>
<i class="fas fa-heart-pulse"></i>
</button>
<button
title="Spectate Player"
class="h-[4.5vh] w-full hover:bg-secondary
relative
before:content-[attr(data-tip)]
before:absolute
before:px-3 before:py-2
before:left-1/2 before:-top-3
before:w-max before:max-w-xs
before:-translate-x-1/2 before:-translate-y-full
before:bg-tertiary before:text-white
before:rounded-md before:opacity-0
before:translate-all
after:absolute
after:left-1/2 after:-top-3
after:h-0 after:w-0
after:-translate-x-1/2 after:border-8
after:border-t-tertiary
after:border-l-transparent
after:border-b-transparent
after:border-r-transparent
after:opacity-0
after:transition-all
hover:before:opacity-100 hover:after:opacity-100
"
data-tip="Spectate Player"
on:click={() =>
SendNUI('clickButton', {
data: 'spectate_player',
selectedData: {
['Player']: {
value: $SELECTED_PLAYER.id,
},
},
})}
>
<i class="fas fa-eye"></i>
</button>
</div>
<div
class="h-[90%] overflow-auto flex flex-col gap-[1vh] select-text"
>
<p class="font-medium text-[1.7vh]">Licenses</p>
<div
class="w-full bg-tertiary rounded-[0.5vh] p-[1.5vh] text-[1.5vh]"
>
<p>
{$SELECTED_PLAYER.discord.replace(
'discord:',
'Discord: ',
)}
</p>
<p>
{$SELECTED_PLAYER.license.replace(
'license:',
'License: ',
)}
</p>
<p>
{$SELECTED_PLAYER.fivem
? $SELECTED_PLAYER.fivem
: ''}
</p>
<p>
{$SELECTED_PLAYER.steam
? $SELECTED_PLAYER.steam
: ''}
</p>
</div>
<p class="font-medium text-[1.7vh]">Information</p>
<div
class="w-full bg-tertiary rounded-[0.5vh] p-[1.5vh] text-[1.5vh]"
>
<p>CID: {$SELECTED_PLAYER.cid}</p>
<p>Name: {$SELECTED_PLAYER.name}</p>
<p>Job: {$SELECTED_PLAYER.job}</p>
<p>Cash: ${$SELECTED_PLAYER.cash}</p>
<p>Bank: ${$SELECTED_PLAYER.bank}</p>
<p>Phone: {$SELECTED_PLAYER.phone}</p>
</div>
<p class="font-medium text-[1.7vh]">Vehicles</p>
{#each $SELECTED_PLAYER.vehicles as vehicle}
<div
class="w-full bg-tertiary flex flex-row rounded-[0.5vh] p-[1.5vh] text-[1.5vh]"
>
<div>
<p class=" font-medium text-[1.7vh]">
{vehicle.label}
</p>
<p>Plate: {vehicle.plate}</p>
</div>
<div class="ml-auto h-full flex items-center">
<button
class="bg-secondary px-[1vh] py-[0.5vh] rounded-[0.5vh] border border-primary"
on:click={() =>
SendNUI('clickButton', {
data: 'spawnPersonalVehicle',
selectedData: {
['VehiclePlate']: {
value: vehicle.plate,
},
},
})}
>
Spawn
</button>
</div>
</div>
{/each}
</div>
</div>
{/if}
</div>
{/if}
{#if banPlayer}
<Modal>
<div class="flex justify-between">
<p class="font-medium text-[1.8vh]">Ban {$SELECTED_PLAYER.name}</p>
<button
class="hover:text-accent"
on:click={() => (banPlayer = false)}
>
<i class="fas fa-xmark"></i>
</button>
</div>
<Input
data={{
label: 'Reason',
value: 'reason',
id: 'reason',
}}
selectedData={SelectData}
/>
<Autofill
action={{
label: 'Duration',
value: 'duration',
id: 'duration',
}}
label_title="Duration"
data={banData}
selectedData={SelectData}
/>
<button
class="h-[3.8vh] px-[1.5vh] rounded-[0.5vh] bg-secondary hover:bg-opacity-90 border-[0.1vh] border-primary"
on:click={() => {
// console.log('Time: ', selectedDataArray['Duration'].value)
// console.log('reason: ', selectedDataArray['Reason'].value)
SendNUI('clickButton', {
data: 'banPlayer',
selectedData: {
['Player']: {
value: $SELECTED_PLAYER.id,
},
['Duration']: {
value: selectedDataArray['Duration'].value,
},
['Reason']: {
value: selectedDataArray['Reason'].value,
},
},
})
}}
>
<p>Ban</p>
</button>
</Modal>
{/if}
{#if kickPlayer}
<Modal>
<div class="flex justify-between">
<p class="font-medium text-[1.8vh]">Kick {$SELECTED_PLAYER.name}</p>
<button
class="hover:text-accent"
on:click={() => (kickPlayer = false)}
>
<i class="fas fa-xmark"></i>
</button>
</div>
<Input
data={{
label: 'Reason',
value: 'reason',
id: 'reason',
}}
selectedData={SelectData}
/>
<button
class="h-[3.8vh] px-[1.5vh] rounded-[0.5vh] bg-secondary hover:bg-opacity-90 border-[0.1vh] border-primary"
on:click={() => {
SendNUI('clickButton', {
data: 'kickPlayer',
selectedData: {
['Player']: {
value: $SELECTED_PLAYER.id,
},
['Reason']: {
value: $SELECTED_PLAYER.id,
},
},
})
}}
>
<p>Kick</p>
</button>
</Modal>
{/if}

View File

@ -0,0 +1,26 @@
<script>
import { PLAYER_VEHICLES, SELECTED_PLAYER } from '@store/players'
import { MENU_WIDE } from '@store/stores'
import { SendNUI } from '@utils/SendNUI'
export let player
async function SelectPlayer(player) {
SELECTED_PLAYER.set(player)
MENU_WIDE.set(true)
const vehicles = await SendNUI('getVehicle', { cid: $SELECTED_PLAYER.cid });
PLAYER_VEHICLES.set(vehicles);
}
</script>
<button
class="h-[4.5vh] w-full flex items-center px-[1.5vh] rounded-[0.5vh] bg-tertiary hover:bg-opacity-90"
on:click={() => {
SelectPlayer(player)
}}
>
<div class="w-full flex items-center justify-between gap-[1vh]">
<p>{player.id} - {player.name}</p>
<i class="fas fa-angle-right" />
</div>
</button>

View File

@ -0,0 +1,42 @@
<script>
import { MENU_WIDE } from '@store/stores'
import { RESOURCE } from '@store/server'
import Header from '@components/Header.svelte'
import ResourceCard from './components/ResourceCard.svelte'
let search = ''
let SortedResources = $RESOURCE ? $RESOURCE.slice().sort((a, b) => a.name.localeCompare(b.name)) : []
</script>
<div class="h-full w-[33vh] px-[2vh]">
<Header
title={'Server'}
hasSearch={true}
onSearchInput={event => (search = event.target.value)}
/>
<div class="w-full h-[84%] flex flex-col gap-[1vh] mt-[1vh] overflow-auto">
{#if $RESOURCE}
{#if $RESOURCE && $RESOURCE.filter((resource) => resource.name.toLowerCase().includes(search.toLowerCase())).length === 0}
<div class="text-tertiary text-center text-[1.7vh] font-medium mt-[1vh]">No Resource Found.</div>
{:else}
{#each SortedResources.filter((resource) => resource.name.toLowerCase().includes(search.toLowerCase())) as resource}
<ResourceCard
label={resource.name}
version={resource.version}
author={resource.author}
description={resource.description}
state={resource.resourceState}
/>
{/each}
{/if}
{/if}
</div>
</div>
{#if $MENU_WIDE}
<div class="h-full w-[66vh] border-l-[0.2vh] border-tertiary px-[2vh]">
<Header title={'Dashboard'} />
</div>
{/if}

View File

@ -0,0 +1,21 @@
<script>
import { RESOURCE } from '@store/server'
import { SendNUI } from '@utils/SendNUI'
export let resource
export let icon
export let state
async function changeState() {
event.stopPropagation();
const data = await SendNUI('setResourceState', { name: resource, state: state });
RESOURCE.set(data);
}
</script>
<button
class="w-[3vh] h-[3vh] rounded-[0.5vh] bg-secondary hover:bg-primary"
on:click={changeState}
>
<i class={icon} />
</button>

View File

@ -0,0 +1,38 @@
<script>
import { slide } from 'svelte/transition'
import ButtonState from './ButtonState.svelte'
export let label
export let version
export let author
export let description
export let state
let dropdownActive
</script>
<button
class="w-full flex justify-between rounded-[0.5vh] bg-tertiary items-center"
on:click={() => (dropdownActive = !dropdownActive)}
>
<div class="h-full p-[2vh] -mr-[8vh] flex flex-col items-start text-start">
<p class="text-[1.8vh] font-medium">{label ? label : ''}</p>
<p class="text-gray-400">{version ? 'Version: ' + version : ''}</p>
<p class="text-gray-400">{author ? 'Author: ' + author : ''}</p>
{#if dropdownActive}
<div transition:slide={{ duration: 150 }}>
<p class="text-gray-400">{description ? description : ''}</p>
</div>
{/if}
</div>
<div class="flex gap-[1vh] h-full py-[1.8vh] pr-[1.8vh]">
{#if state == 'started'}
<ButtonState icon="fas fa-stop" resource={label} state={'stop'} />
<ButtonState icon="fas fa-arrows-rotate" resource={label} state={'restart'} />
{:else}
<ButtonState icon="fas fa-play" resource={label} state={'start'} />
{/if}
</div>
</button>

View File

@ -0,0 +1,68 @@
<script lang="ts">
import { ACTION } from '@store/actions'
import { ITEM_DATA, VEHICLE_DATA, JOB_DATA, GANG_DATA, LOCATION_DATA, PED_LIST } from '@store/data'
import { PLAYER, PLAYER_DATA } from '@store/players'
import { RESOURCE, RESOURCES, COMMANDS } from '@store/server'
import { VEHICLE_DEV } from '@store/vehicle_dev'
import { TOGGLE_COORDS } from '@store/togglecoords'
import { Message, Messages } from "@store/staffchat";
import { ReceiveNUI } from '@utils/ReceiveNUI'
import { debugData } from '@utils/debugData'
import { ENTITY_INFO } from '@store/entityInfo'
debugData([
{
action: 'setVisible',
data: true,
},
])
debugData([
{
action: 'setBrowserMode',
data: true
},
])
ReceiveNUI('setupUI', (data: any) => {
$ACTION = data.actions
$RESOURCE = data.resources
$PLAYER_DATA = data.playerData
$COMMANDS = data.commands
})
ReceiveNUI('setResourceData', (data: any) => {
$RESOURCE = data
})
ReceiveNUI('setPlayersData', (data: any) => {
$PLAYER = data
})
ReceiveNUI('data', (data: any) => {
$VEHICLE_DATA = data.vehicles
$ITEM_DATA = data.items
$JOB_DATA = data.jobs
$GANG_DATA = data.gangs
$LOCATION_DATA = data.locations
$PED_LIST = data.pedlist
})
ReceiveNUI('showVehicleMenu', (data: any) => {
$VEHICLE_DEV = data
})
ReceiveNUI('showCoordsMenu', (data: any) => {
$TOGGLE_COORDS = data
})
ReceiveNUI('showEntityInfo', (data: any) => {
$ENTITY_INFO = data
})
ReceiveNUI('setMessages', (data: any) => {
Message.set(data)
Messages.set($Message[0])
});
</script>

View File

@ -0,0 +1,192 @@
<script lang="ts">
import { onMount } from "svelte";
const vertexShaders = `
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;
}
`;
const fragmentShaderSource = `
varying highp vec2 textureCoordinate;
uniform sampler2D external_texture;
void main()
{
gl_FragColor = texture2D(external_texture, textureCoordinate);
}
`;
const makeShader = (gl: WebGLRenderingContext, type: number, src: string) => {
const shader = gl.createShader(type);
gl.shaderSource(shader, src);
gl.compileShader(shader);
const infoLog = gl.getShaderInfoLog(shader);
if (infoLog) {
console.error(infoLog);
}
return shader;
};
const createTexture = (gl: WebGLRenderingContext) => {
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;
};
const createBuffers = (gl: WebGLRenderingContext) => {
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 };
};
const createProgram = (gl: WebGLRenderingContext) => {
const vertexShader = makeShader(gl, gl.VERTEX_SHADER, vertexShaders);
const fragmentShader = makeShader(
gl,
gl.FRAGMENT_SHADER,
fragmentShaderSource
);
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 };
};
const createGameView = (canvas: HTMLCanvasElement) => {
// console.log(canvas);
const gl = canvas.getContext("webgl", {
antialias: false,
depth: false,
stencil: false,
alpha: false,
desynchronized: true,
failIfMajorPerformanceCaveat: false,
}) as WebGLRenderingContext;
let render = () => {};
function createStuff() {
const tex = createTexture(gl);
const { program, vloc, tloc } = createProgram(gl);
const { vertexBuff, texBuff } = 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();
}
const gameView = {
canvas,
gl,
animationFrame: void 0,
resize: (width: number, height: number) => {
gl.viewport(0, 0, width, height);
gl.canvas.width = width;
gl.canvas.height = height;
},
};
render = () => {
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
gl.finish();
gameView.animationFrame = requestAnimationFrame(render);
};
createStuff();
return gameView;
};
let canvasRef: HTMLCanvasElement;
onMount(() => {
createGameView(canvasRef);
});
</script>
<div>
<canvas
bind:this={canvasRef}
width={window.innerWidth}
height={window.innerHeight}
/>
</div>
<style>
div {
position: absolute;
left: 0;
top: 0;
}
</style>

View File

@ -0,0 +1,452 @@
<script>
import { debugData } from '../utils/debugData';
let show = false;
//EXAMPLE
const debugActions = [
{
id: "admin_car",
label: "Admin Car",
type: "client",
perms: "mod",
event: "ps-adminmenu:client:admincar",
},
{
id: "ban_player",
label: "Ban Player",
type: "client",
perms: "mod",
dropdown: [
{ label: "Player", option: "dropdown", data: "players" },
{ label: "Reason", option: "text" },
{ label: "Time", option: "dropdown",
data: [
{ label: "1 time", value: "1000" },
{ label: "2 time", value: "2000" },
{ label: "3 time", value: "3000" },
],
},
{ label: "Ban", type: "server", option: "button", event: "ps-adminmenu:client:banplayer" },
],
},
{
id: "noclip",
label: "Noclip",
type: "client",
perms: "mod",
event: "ps-adminmenu:client:noclip",
},
{
id: "invisible",
label: "Invisible",
type: "client",
perms: "mod",
event: "ps-adminmenu:client:invisible",
},
{
id: "kick_player",
label: "Kick Player",
type: "client",
perms: "mod",
event: "ps-adminmenu:client:kickplayer",
dropdown: [
{ label: "Player", option: "dropdown", data: "players" },
{ label: "Reason", option: "text" },
{ label: "Time", option: "dropdown",
data: [
{ label: "1 time", value: "1000" },
{ label: "2 time", value: "2000" },
{ label: "3 time", value: "3000" },
],
},
{ label: "Ban", type: "server", option: "button", event: "ps-adminmenu:client:banplayer" },
],
},
{
id: "spawn_vehicle",
label: "Spawn Vehicle",
type: "client",
perms: "mod",
dropdown: [
{ label: "Player", option: "dropdown", data: "players" },
{ label: "Reason", option: "text" },
{ label: "Time", option: "dropdown",
data: [
{ label: "1 time", value: "1000" },
{ label: "2 time", value: "2000" },
{ label: "3 time", value: "3000" },
],
},
{ label: "Ban", type: "server", option: "button", event: "ps-adminmenu:client:banplayer" },
],
},
{
id: "spawn_vehicle",
label: "Spawn Vehicle",
type: "client",
perms: "mod",
dropdown: [
{ label: "Player", option: "dropdown", data: "players" },
{ label: "Reason", option: "text" },
{ label: "Time", option: "dropdown",
data: [
{ label: "1 time", value: "1000" },
{ label: "2 time", value: "2000" },
{ label: "3 time", value: "3000" },
],
},
{ label: "Ban", type: "server", option: "button", event: "ps-adminmenu:client:banplayer" },
],
},
{
id: "spawn_vehicle",
label: "Spawn Vehicle",
type: "client",
perms: "mod",
dropdown: [
{ label: "Player", option: "dropdown", data: "players" },
{ label: "Reason", option: "text" },
{ label: "Time", option: "dropdown",
data: [
{ label: "1 time", value: "1000" },
{ label: "2 time", value: "2000" },
{ label: "3 time", value: "3000" },
],
},
{ label: "Ban", type: "server", option: "button", event: "ps-adminmenu:client:banplayer" },
],
},
{
id: "spawn_vehicle",
label: "Spawn Vehicle",
type: "client",
perms: "mod",
dropdown: [
{ label: "Player", option: "dropdown", data: "players" },
{ label: "Reason", option: "text" },
{ label: "Time", option: "dropdown",
data: [
{ label: "1 time", value: "1000" },
{ label: "2 time", value: "2000" },
{ label: "3 time", value: "3000" },
],
},
{ label: "Ban", type: "server", option: "button", event: "ps-adminmenu:client:banplayer" },
],
},
{
id: "spawn_vehicle",
label: "Spawn Vehicle",
type: "client",
perms: "mod",
dropdown: [
{ label: "Player", option: "dropdown", data: "players" },
{ label: "Reason", option: "text" },
{ label: "Time", option: "dropdown",
data: [
{ label: "1 time", value: "1000" },
{ label: "2 time", value: "2000" },
{ label: "3 time", value: "3000" },
],
},
{ label: "Ban", type: "server", option: "button", event: "ps-adminmenu:client:banplayer" },
],
},
{
id: "spawn_vehicle",
label: "Spawn Vehicle",
type: "client",
perms: "mod",
dropdown: [
{ label: "Player", option: "dropdown", data: "players" },
{ label: "Reason", option: "text" },
{ label: "Time", option: "dropdown",
data: [
{ label: "1 time", value: "1000" },
{ label: "2 time", value: "2000" },
{ label: "3 time", value: "3000" },
],
},
{ label: "Ban", type: "server", option: "button", event: "ps-adminmenu:client:banplayer" },
],
},
{
id: "spawn_vehicle",
label: "Spawn Vehicle",
type: "client",
perms: "mod",
dropdown: [
{ label: "Player", option: "dropdown", data: "players" },
{ label: "Reason", option: "text" },
{ label: "Time", option: "dropdown",
data: [
{ label: "1 time", value: "1000" },
{ label: "2 time", value: "2000" },
{ label: "3 time", value: "3000" },
],
},
{ label: "Ban", type: "server", option: "button", event: "ps-adminmenu:client:banplayer" },
],
},
]
const debugResources = [
{
name: "ps-adminmenu",
resourceState: "started",
},
{
name: "ps-mdt",
version: "1.0.0",
description: "A cool mdt",
author: "Project Sloth",
resourceState: "started",
},
{
name: "ps-dispatch",
version: "1.0.0",
description: "A cool dispatch",
author: "Project Sloth",
resourceState: "started",
},
{
name: "ps-hosuing",
version: "1.0.0",
description: "A cool house",
author: "Project Sloth",
resourceState: "started",
},
{
name: "ps-camera",
version: "1.0.0",
description: "A cool camera",
author: "Project Sloth and ok1ez ok1ez",
resourceState: "started",
},
{
name: "ps-hud",
version: "1.0.0",
description: "A cool hud",
author: "Project Sloth",
resourceState: "stopped",
},
{
name: "ps-fuel",
version: "1.0.0",
description: "A cool gas pump",
author: "Project Sloth",
resourceState: "stopped",
},
{
name: "ps-liveries",
version: "1.0.0",
description: "A cool liverie",
author: "Project Sloth",
resourceState: "stopped",
},
{
name: "ps-ui",
version: "1.0.0",
description: "A cool ui",
author: "Project Sloth",
resourceState: "stopped",
},
]
const debugPlayers = [
{
id: "1",
citizenid: "ERP95808",
name: "John Doe",
job: "Police Officer",
phone: "555-555-5555",
discord: "discord:917110675220865025",
dateofbirth: "01/12/2001",
bank: "10022",
cash: "2022",
license: "license:9e9df5e3b52641da00f5b2aba25edc45317689b2",
},
{
id: "2",
citizenid: "ERP87521",
name: "Jane Smith",
job: "Paramedic",
phone: "555-555-1234",
discord: "discord:732198415678290144",
dateofbirth: "05/18/1990",
bank: "8000",
cash: "150",
license: "license:5a0f4e86c7d283b3cde6acba9821d4a5913076d8",
},
{
id: "3",
citizenid: "ERP35267",
name: "Michael Johnson",
job: "Mechanic",
phone: "555-555-9876",
discord: "discord:609827518329704632",
dateofbirth: "11/03/1985",
bank: "500",
cash: "3500",
license: "license:c5f2b76a8e1e0d4c7892a3d1b74cf561b89e25e7",
},
{
id: "4",
citizenid: "ERP70125",
name: "Emily Davis",
job: "Lawyer",
phone: "555-555-2222",
discord: "discord:815369027403189267",
dateofbirth: "09/21/1988",
bank: "22000",
cash: "500",
license: "license:3d4e6f7aa1b9e8c5d2fbc0439e1a865b470192f4",
},
{
id: "5",
citizenid: "ERP48039",
name: "Robert Wilson",
job: "Taxi Driver",
phone: "555-555-7777",
discord: "discord:518942015678302479",
dateofbirth: "07/08/1977",
bank: "1200",
cash: "780",
license: "license:98e7c6d5a2b3f1e4d0c9876a5432109bfedc8a76",
},
{
id: "6",
citizenid: "ERP91726",
name: "Amanda Lee",
job: "Chef",
phone: "555-555-3333",
discord: "discord:725048390162871590",
dateofbirth: "03/15/1995",
bank: "4000",
cash: "200",
license: "license:4a5b6c7d8e9f01234567890abcdef1234567890",
},
{
id: "7",
citizenid: "ERP24680",
name: "Christopher Martinez",
job: "Firefighter",
phone: "555-555-8888",
discord: "discord:926371058274690831",
dateofbirth: "12/30/1982",
bank: "7500",
cash: "1000",
license: "license:7890123456abcdef0123456789a5b4c3d2e1f0",
},
]
debugData([
{
action: "setActionData",
data: debugActions
},
])
debugData([
{
action: "setResourceData",
data: debugResources
},
])
debugData([
{
action: "setPlayersData",
data: debugPlayers
},
])
let options = [
{
component: 'Show',
actions : [
{
name: "show",
action: "setVisible",
data: true,
},
{
name: "hide",
action: "setVisible",
data: false,
},
]
},
{
component: 'Actions Data',
actions : [
{
name: "Set Data",
action: "setActionData",
data: debugActions,
},
]
},
{
component: 'Resource Data',
actions : [
{
name: "Set Data",
action: "setResourceData",
data: debugResources,
},
]
},
{
component: 'Player Data',
actions : [
{
name: "Set Data",
action: "setPlayersData",
data: debugPlayers,
},
]
},
]
</script>
<div class="absolute top-0 z-[1000] font-medium uppercase m-4">
<button class="bg-neutral-800 p-3
3 font-medium uppercase"
on:click={() => {
show = !show;
}}
>
Show
</button>
{#if show}
<div class="w-fit h-fit bg-neutral-800 p-2 ">
{#each options as option}
<div class="flex flex-row gap-2 items-center m-1">
<p class="h-full w-full mr-2">{option.component}</p>
{#each option.actions as action}
<button class="bg-neutral-600 p-2"
on:click={() => {
if (action.custom == true) {
action.customFunction();
return
}
debugData([
{
action: action.action,
data: action.data,
},
])
}}
>
{action.name}
</button>
{/each}
</div>
{/each}
</div>
{/if}
</div>

View File

@ -0,0 +1,69 @@
<script lang="ts">
import { ReceiveNUI } from '../utils/ReceiveNUI'
import { SendNUI } from '../utils/SendNUI'
import { onMount } from 'svelte'
import { BROWSER_MODE, MENU_WIDE, VISIBILITY } from '../store/stores'
import BackdropFix from './BackdropFix.svelte'
let isVisible: boolean
let isBrowser: boolean
BROWSER_MODE.subscribe((browser: boolean) => {
isBrowser = browser
})
VISIBILITY.subscribe((visible: boolean) => {
isVisible = visible
})
ReceiveNUI<boolean>('setVisible', (visible: boolean) => {
VISIBILITY.set(visible)
})
ReceiveNUI('setBrowserMode', (data: boolean) => {
BROWSER_MODE.set(data)
// console.log('browser mode enabled')
})
onMount(() => {
const keyHandler = (e: KeyboardEvent) => {
if (isVisible && ['Escape'].includes(e.code)) {
SendNUI('hideUI')
VISIBILITY.set(false)
}
if (
!isVisible &&
['Escape'].includes(e.code) &&
isBrowser === true
) {
VISIBILITY.set(true)
}
}
window.addEventListener('keydown', keyHandler)
return () => window.removeEventListener('keydown', keyHandler)
})
</script>
{#if isVisible}
<main class="w-screen h-screen flex justify-end items-center {!$MENU_WIDE ? " " : "justify-center"}">
<slot />
</main>
<!-- <BackdropFix /> -->
{/if}
<style>
main {
position: absolute;
left: 0;
top: 0;
z-index: 100;
user-select: none;
box-sizing: border-box;
padding: 0;
margin: 0;
height: 100vh;
width: 100vw;
}
</style>

View File

@ -0,0 +1,48 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
* {
margin: 0;
padding: 0;
font-smooth: auto;
}
*:focus {
outline: none;
}
:root {
font-size: 62.5%;
font-smooth: auto;
color: #c2c2c2;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
html,
body {
height: 100vh;
width: 100vw;
font-size: 1.6rem;
overflow: hidden;
}
::-webkit-scrollbar {
height: 0px;
width: 0vh;
background-color: #24272b;
}
.scroll-visble ::-webkit-scrollbar {
width: 0.5vh;
background-color: #141517;
}
::-webkit-scrollbar-thumb {
background-color: #24272b;
border-radius: 50px;
}

View File

@ -0,0 +1,2 @@
/// <reference types="svelte" />
/// <reference types="vite/client" />

View File

@ -0,0 +1,7 @@
import sveltePreprocess from 'svelte-preprocess'
export default {
// Consult https://github.com/sveltejs/svelte-preprocess
// for more information about preprocessors
preprocess: sveltePreprocess()
}

View File

@ -0,0 +1,20 @@
module.exports = {
darkmode: true,
content: [
"./index.html",
"./src/**/*.{svelte,js,ts,jsx,tsx}",
],
theme: {
extend: {
colors: {
primary: '#141517',
secondary: '#1a1b1e',
tertiary: '#24272b',
accent: '#2284d9',
border_primary: '#373a40',
hover_secondary: '#2c2e33',
}
},
},
plugins: [],
}

View File

@ -0,0 +1,29 @@
{
"extends": "@tsconfig/svelte/tsconfig.json",
"compilerOptions": {
"target": "esnext",
"useDefineForClassFields": true,
"module": "esnext",
"resolveJsonModule": true,
"baseUrl": ".",
"paths": {
"@assets/*": ["src/assets/*"],
"@components/*": ["src/components/*"],
"@providers/*": ["src/providers/*"],
"@store/*": ["src/store/*"],
"@utils/*": ["src/utils/*"],
"@typings/*": ["src/typings/*"],
"@layout/*": ["src/layout/*"],
"@pages/*": ["src/pages/*"],
},
/**
* Typecheck JS in `.svelte` and `.js` files by default.
* Disable checkJs if you'd like to use dynamic types in JS.
* Note that setting allowJs false does not prevent the use
* of JS in `.svelte` files.
*/
"allowJs": true,
"checkJs": true
},
"include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"]
}

View File

@ -0,0 +1,41 @@
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
import postcss from './postcss.config.js';
import { resolve } from "path";
// https://vitejs.dev/config/
export default defineConfig({
css: {
postcss,
},
plugins: [svelte({
/* plugin options */
})],
base: './', // fivem nui needs to have local dir reference
resolve: {
alias: {
"@assets": resolve("./src/assets"),
"@components": resolve("./src/components"),
"@providers": resolve("./src/providers"),
"@store": resolve("./src/store"),
"@utils": resolve("./src/utils"),
"@typings": resolve("./src/typings"),
"@layout": resolve("./src/layout"),
"@pages": resolve("./src/pages"),
},
},
build: {
emptyOutDir: true,
outDir: '../html',
assetsDir: './',
rollupOptions: {
output: {
// By not having hashes in the name, you don't have to update the manifest, yay!
entryFileNames: `[name].js`,
chunkFileNames: `[name].js`,
assetFileNames: `[name].[ext]`
}
}
}
})