mirror of
https://github.com/CommunityOx/ox_lib.git
synced 2026-08-17 15:06:02 +01:00
Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8bf225b3d | ||
|
|
1e02469d9a | ||
|
|
4b2446f7a8 | ||
|
|
f33c613b6d | ||
|
|
e66b6805bc | ||
|
|
fdc1674f4f | ||
|
|
c8db003cbc | ||
|
|
ae16b78bc4 | ||
|
|
3ea020cc50 | ||
|
|
db0bd16f0f | ||
|
|
01935adf8e | ||
|
|
220c0c91ec | ||
|
|
855e1e4a36 | ||
|
|
a2f6b9a9e9 | ||
|
|
13a24baee4 | ||
|
|
df7a6b9c21 | ||
|
|
faa9dda57f | ||
|
|
619ecc9e55 | ||
|
|
56c0a91022 | ||
|
|
d6417b9a84 | ||
|
|
ac9625d33a | ||
|
|
2ab9114d2f | ||
|
|
88c68c7068 | ||
|
|
1e178180bb | ||
|
|
fef441c456 | ||
|
|
85a3879a03 | ||
|
|
65813e75e6 | ||
|
|
a9648fb45f | ||
|
|
dec6caa63b | ||
|
|
967682c449 | ||
|
|
301ff2daf8 | ||
|
|
fd8d65166b | ||
|
|
581640c07a | ||
|
|
9fbf0222ad | ||
|
|
753d3eb025 | ||
|
|
815caddb02 | ||
|
|
3c5d5faa9f | ||
|
|
ae640729ca | ||
|
|
3c405250b0 | ||
|
|
7375e4054d | ||
|
|
22b3600a14 | ||
|
|
b5fdd8a160 | ||
|
|
ba790f9830 | ||
|
|
9ec43862a2 | ||
|
|
2d2575963d | ||
|
|
8039539158 | ||
|
|
f59beb2af6 | ||
|
|
ab0dac8fde | ||
|
|
4491ad3599 | ||
|
|
a9728663ca |
12
.github/actions/bump-manifest-version.js
vendored
12
.github/actions/bump-manifest-version.js
vendored
@@ -1,10 +1,8 @@
|
|||||||
const fs = require('fs');
|
const fxManifest = await Bun.file('./fxmanifest.lua').text();
|
||||||
|
|
||||||
const version = process.env.TGT_RELEASE_VERSION;
|
let newVersion = process.env.TGT_RELEASE_VERSION;
|
||||||
const newVersion = version.replace('v', '');
|
newVersion = newVersion.replace('v', '')
|
||||||
|
|
||||||
const manifestFile = fs.readFileSync('fxmanifest.lua', { encoding: 'utf8' });
|
const newFileContent = fxManifest.replace(/\bversion\s+(.*)$/gm, `version '${newVersion}'`);
|
||||||
|
|
||||||
const newFileContent = manifestFile.replace(/\bversion\s+(.*)$/gm, `version '${newVersion}'`);
|
await Bun.write('./fxmanifest.lua', newFileContent);
|
||||||
|
|
||||||
fs.writeFileSync('fxmanifest.lua', newFileContent);
|
|
||||||
|
|||||||
12
.github/actions/bump-package-version.js
vendored
12
.github/actions/bump-package-version.js
vendored
@@ -1,10 +1,6 @@
|
|||||||
const fs = require('fs');
|
const packageJson = await Bun.file('./package/package.json').json();
|
||||||
|
|
||||||
const version = process.env.TGT_RELEASE_VERSION;
|
const newVersion = process.env.TGT_RELEASE_VERSION;
|
||||||
const newVersion = version.replace('v', '');
|
packageJson.version = newVersion.replace('v', '');
|
||||||
|
|
||||||
const packageFile = fs.readFileSync('package/package.json', { encoding: 'utf8' });
|
await Bun.write('./package/package.json', JSON.stringify(packageJson, null, 2));
|
||||||
|
|
||||||
const newFileContent = packageFile.replace(/"version":\s+"(.*)",$/gm, `"version": "${newVersion}",`);
|
|
||||||
|
|
||||||
fs.writeFileSync('package/package.json', newFileContent);
|
|
||||||
|
|||||||
84
.github/workflows/release.yml
vendored
84
.github/workflows/release.yml
vendored
@@ -5,46 +5,49 @@ on:
|
|||||||
tags:
|
tags:
|
||||||
- 'v*.*.*'
|
- 'v*.*.*'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
id-token: write # Required for OIDC
|
||||||
|
contents: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
create-release:
|
create-release:
|
||||||
|
if: github.actor_id != 210085057
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Get latest code
|
- name: Install zip
|
||||||
uses: actions/checkout@v4
|
run: sudo apt install zip
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
ref: ${{ github.event.repository.default_branch }}
|
|
||||||
|
|
||||||
- name: Setup Bun
|
- name: Install Bun
|
||||||
uses: oven-sh/setup-bun@v2
|
uses: oven-sh/setup-bun@v2
|
||||||
with:
|
with:
|
||||||
bun-version: latest
|
bun-version: latest
|
||||||
|
|
||||||
|
|
||||||
- name: Install package dependencies
|
- name: Generate GitHub App token
|
||||||
run: bun install
|
id: app_token
|
||||||
working-directory: package
|
uses: tibdex/github-app-token@v2
|
||||||
|
with:
|
||||||
|
app_id: ${{ secrets.APP_ID }}
|
||||||
|
private_key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||||
|
|
||||||
- name: Install web dependencies
|
- name: Get latest code
|
||||||
run: bun install
|
uses: actions/checkout@v4
|
||||||
working-directory: web
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
- name: Run web build script
|
ref: ${{ github.event.repository.default_branch }}
|
||||||
run: bun run build
|
token: ${{ steps.app_token.outputs.token }}
|
||||||
working-directory: web
|
|
||||||
|
|
||||||
|
|
||||||
- name: Bump package version
|
- name: Bump package version
|
||||||
run: bun .github/actions/bump-package-version.js
|
run: bun run .github/actions/bump-package-version.js
|
||||||
env:
|
env:
|
||||||
TGT_RELEASE_VERSION: ${{ github.ref_name }}
|
TGT_RELEASE_VERSION: ${{ github.ref_name }}
|
||||||
|
|
||||||
- name: Bump manifest version
|
- name: Bump manifest version
|
||||||
run: bun .github/actions/bump-manifest-version.js
|
run: bun run .github/actions/bump-manifest-version.js
|
||||||
env:
|
env:
|
||||||
TGT_RELEASE_VERSION: ${{ github.ref_name }}
|
TGT_RELEASE_VERSION: ${{ github.ref_name }}
|
||||||
|
|
||||||
|
|
||||||
- name: Push version bump change
|
- name: Push version bump change
|
||||||
uses: EndBug/add-and-commit@v9
|
uses: EndBug/add-and-commit@v9
|
||||||
with:
|
with:
|
||||||
@@ -53,25 +56,23 @@ jobs:
|
|||||||
default_author: github_actions
|
default_author: github_actions
|
||||||
message: 'chore: bump version to ${{ github.ref_name }}'
|
message: 'chore: bump version to ${{ github.ref_name }}'
|
||||||
|
|
||||||
- name: Update tag
|
|
||||||
uses: EndBug/latest-tag@v1
|
|
||||||
with:
|
|
||||||
ref: ${{ github.ref_name }}
|
|
||||||
|
|
||||||
|
- name: Install package dependencies
|
||||||
- name: Publish package to npm registry
|
run: bun install --frozen-lockfile
|
||||||
run: bun publish --access public
|
|
||||||
working-directory: package
|
working-directory: package
|
||||||
env:
|
|
||||||
NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
||||||
|
|
||||||
- name: Install zip
|
- name: Install web dependencies
|
||||||
run: sudo apt install zip
|
run: bun install --frozen-lockfile
|
||||||
|
working-directory: web
|
||||||
|
|
||||||
|
- name: Run web build script
|
||||||
|
run: bun run build
|
||||||
|
working-directory: web
|
||||||
|
|
||||||
|
|
||||||
- name: Bundle files
|
- name: Bundle files
|
||||||
run: |
|
run: |
|
||||||
mkdir -p ./temp/ox_lib
|
mkdir -p ./temp/ox_lib/web
|
||||||
mkdir -p ./temp/ox_lib/web/
|
|
||||||
cp ./{LICENSE,README.md,fxmanifest.lua,init.lua} ./temp/ox_lib
|
cp ./{LICENSE,README.md,fxmanifest.lua,init.lua} ./temp/ox_lib
|
||||||
cp -r ./{imports,resource,locales} ./temp/ox_lib
|
cp -r ./{imports,resource,locales} ./temp/ox_lib
|
||||||
cp -r ./web/build ./temp/ox_lib/web/
|
cp -r ./web/build ./temp/ox_lib/web/
|
||||||
@@ -79,11 +80,22 @@ jobs:
|
|||||||
|
|
||||||
- name: Create release
|
- name: Create release
|
||||||
uses: 'marvinpinto/action-automatic-releases@v1.2.1'
|
uses: 'marvinpinto/action-automatic-releases@v1.2.1'
|
||||||
id: auto_release
|
|
||||||
with:
|
with:
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
prerelease: false
|
prerelease: false
|
||||||
files: ox_lib.zip
|
files: ox_lib.zip
|
||||||
env:
|
|
||||||
CI: false
|
- name: Update tag
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
uses: EndBug/latest-tag@v1
|
||||||
|
with:
|
||||||
|
ref: ${{ github.ref_name }}
|
||||||
|
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '24'
|
||||||
|
|
||||||
|
- name: Publish package to npm registry
|
||||||
|
run: npm publish --access public
|
||||||
|
working-directory: package
|
||||||
|
|||||||
@@ -27,6 +27,6 @@ https://www.npmjs.com/package/@communityox/ox_lib
|
|||||||
## 🖥️ Lua Language Server
|
## 🖥️ Lua Language Server
|
||||||
|
|
||||||
- Install [Lua Language Server](https://marketplace.visualstudio.com/items?itemName=sumneko.lua) to ease development with annotations, type checking, diagnostics, and more.
|
- Install [Lua Language Server](https://marketplace.visualstudio.com/items?itemName=sumneko.lua) to ease development with annotations, type checking, diagnostics, and more.
|
||||||
- Install [cfxlua-vscode](https://marketplace.visualstudio.com/items?itemName=communityox.cfxlua-vscode) to add natives and cfxlua runtime declarations to LLS.
|
- Install [CfxLua IntelliSense](https://marketplace.visualstudio.com/items?itemName=communityox.cfxlua-vscode-cox) to add natives and cfxlua runtime declarations to LLS.
|
||||||
- You can load ox_lib into your global development environment by modifying workspace/user settings "Lua.workspace.library" with the resource path.
|
- You can load ox_lib into your global development environment by modifying workspace/user settings "Lua.workspace.library" with the resource path.
|
||||||
- e.g. "c:/fxserver/resources/ox_lib"
|
- e.g. "c:/fxserver/resources/ox_lib"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ rdr3_warning 'I acknowledge that this is a prerelease build of RedM, and I am aw
|
|||||||
|
|
||||||
name 'ox_lib'
|
name 'ox_lib'
|
||||||
author 'Overextended'
|
author 'Overextended'
|
||||||
version '3.30.7'
|
version '3.32.3'
|
||||||
license 'LGPL-3.0-or-later'
|
license 'LGPL-3.0-or-later'
|
||||||
repository 'https://github.com/communityox/ox_lib'
|
repository 'https://github.com/communityox/ox_lib'
|
||||||
description 'A library of shared functions to utilise in other resources.'
|
description 'A library of shared functions to utilise in other resources.'
|
||||||
|
|||||||
@@ -84,6 +84,29 @@ function lib.dui:sendMessage(message)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param x number
|
||||||
|
---@param y number
|
||||||
|
function lib.dui:sendMouseMove(x, y)
|
||||||
|
SendDuiMouseMove(self.duiObject, x, y)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param button 'left' | 'middle' | 'right'
|
||||||
|
function lib.dui:sendMouseDown(button)
|
||||||
|
SendDuiMouseDown(self.duiObject, button)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param button 'left' | 'middle' | 'right'
|
||||||
|
function lib.dui:sendMouseUp(button)
|
||||||
|
SendDuiMouseUp(self.duiObject, button)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param deltaX number
|
||||||
|
---@param deltaY number
|
||||||
|
function lib.dui:sendMouseWheel(deltaX, deltaY)
|
||||||
|
SendDuiMouseWheel(self.duiObject, deltaY, deltaX)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
AddEventHandler('onResourceStop', function(resourceName)
|
AddEventHandler('onResourceStop', function(resourceName)
|
||||||
if cache.resource ~= resourceName then return end
|
if cache.resource ~= resourceName then return end
|
||||||
|
|
||||||
|
|||||||
@@ -8,25 +8,29 @@
|
|||||||
|
|
||||||
---@param coords vector3 The coords to check from.
|
---@param coords vector3 The coords to check from.
|
||||||
---@param maxDistance? number The max distance to check.
|
---@param maxDistance? number The max distance to check.
|
||||||
|
---@param ignorePlayerId? number|false The player server ID to ignore.
|
||||||
---@return number? playerId
|
---@return number? playerId
|
||||||
---@return number? playerPed
|
---@return number? playerPed
|
||||||
---@return vector3? playerCoords
|
---@return vector3? playerCoords
|
||||||
function lib.getClosestPlayer(coords, maxDistance)
|
function lib.getClosestPlayer(coords, maxDistance, ignorePlayerId)
|
||||||
local players = GetActivePlayers()
|
local players = GetActivePlayers()
|
||||||
local closestId, closestPed, closestCoords
|
local closestId, closestPed, closestCoords
|
||||||
maxDistance = maxDistance or 2.0
|
maxDistance = maxDistance or 2.0
|
||||||
|
|
||||||
for i = 1, #players do
|
for i = 1, #players do
|
||||||
local playerId = players[i]
|
local playerId = players[i]
|
||||||
local playerPed = GetPlayerPed(playerId)
|
|
||||||
local playerCoords = GetEntityCoords(playerPed)
|
|
||||||
local distance = #(coords - playerCoords)
|
|
||||||
|
|
||||||
if distance < maxDistance then
|
if not ignorePlayerId or playerId ~= ignorePlayerId then
|
||||||
maxDistance = distance
|
local playerPed = GetPlayerPed(playerId)
|
||||||
closestId = playerId
|
local playerCoords = GetEntityCoords(playerPed)
|
||||||
closestPed = playerPed
|
local distance = #(coords - playerCoords)
|
||||||
closestCoords = playerCoords
|
|
||||||
|
if distance < maxDistance then
|
||||||
|
maxDistance = distance
|
||||||
|
closestId = playerId
|
||||||
|
closestPed = playerPed
|
||||||
|
closestCoords = playerCoords
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ end
|
|||||||
|
|
||||||
if service == 'fivemanage' then
|
if service == 'fivemanage' then
|
||||||
local key = GetConvar('fivemanage:key', '')
|
local key = GetConvar('fivemanage:key', '')
|
||||||
|
local dataset = GetConvar('fivemanage:dataset', '')
|
||||||
|
|
||||||
if key ~= '' then
|
if key ~= '' then
|
||||||
local endpoint = 'https://api.fivemanage.com/api/logs/batch'
|
local endpoint = 'https://api.fivemanage.com/api/logs/batch'
|
||||||
@@ -101,9 +102,13 @@ if service == 'fivemanage' then
|
|||||||
local headers = {
|
local headers = {
|
||||||
['Content-Type'] = 'application/json',
|
['Content-Type'] = 'application/json',
|
||||||
['Authorization'] = key,
|
['Authorization'] = key,
|
||||||
['User-Agent'] = 'ox_lib'
|
['User-Agent'] = 'ox_lib',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if dataset ~= "" then
|
||||||
|
headers['X-Fivemanage-Dataset'] = dataset
|
||||||
|
end
|
||||||
|
|
||||||
function lib.logger(source, event, message, ...)
|
function lib.logger(source, event, message, ...)
|
||||||
if not buffer then
|
if not buffer then
|
||||||
buffer = {}
|
buffer = {}
|
||||||
@@ -123,17 +128,43 @@ if service == 'fivemanage' then
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local metadata = {
|
||||||
|
hostname = hostname,
|
||||||
|
service = event,
|
||||||
|
source = source,
|
||||||
|
}
|
||||||
|
|
||||||
|
local playerTags = formatTags(source, nil)
|
||||||
|
if playerTags and type(playerTags) == 'string' then
|
||||||
|
local tempTable = { string.strsplit(',', playerTags) }
|
||||||
|
for _, v in pairs(tempTable) do
|
||||||
|
local key, value = string.strsplit(':', v)
|
||||||
|
if key and value then
|
||||||
|
metadata[key] = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local args = { ... }
|
||||||
|
for _, arg in pairs(args) do
|
||||||
|
if type(arg) == 'table' then
|
||||||
|
for k, v in pairs(arg) do
|
||||||
|
metadata[k] = v
|
||||||
|
end
|
||||||
|
elseif type(arg) == 'string' then
|
||||||
|
local key, value = string.strsplit(':', arg)
|
||||||
|
if key and value then
|
||||||
|
metadata[key] = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
bufferSize += 1
|
bufferSize += 1
|
||||||
buffer[bufferSize] = {
|
buffer[bufferSize] = {
|
||||||
level = "info",
|
level = "info",
|
||||||
message = message,
|
message = message,
|
||||||
resource = cache.resource,
|
resource = cache.resource,
|
||||||
metadata = {
|
metadata = metadata,
|
||||||
hostname = hostname,
|
|
||||||
service = event,
|
|
||||||
source = source,
|
|
||||||
tags = formatTags(source, ... and string.strjoin(',', string.tostringall(...)) or nil),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -9,14 +9,8 @@
|
|||||||
---@diagnostic disable: param-type-mismatch
|
---@diagnostic disable: param-type-mismatch
|
||||||
lib.marker = {}
|
lib.marker = {}
|
||||||
|
|
||||||
local defaultRotation = vector3(0, 0, 0)
|
---@enum (key) MarkerType
|
||||||
local defaultDirection = vector3(0, 0, 0)
|
local markerTypes = {
|
||||||
local defaultColor = { r = 255, g = 255, b = 255, a = 100 }
|
|
||||||
local defaultSize = { width = 2, height = 1 }
|
|
||||||
local defaultTextureDict = nil
|
|
||||||
local defaultTextureName = nil
|
|
||||||
|
|
||||||
local markerTypesMap = {
|
|
||||||
UpsideDownCone = 0,
|
UpsideDownCone = 0,
|
||||||
VerticalCylinder = 1,
|
VerticalCylinder = 1,
|
||||||
ThickChevronUp = 2,
|
ThickChevronUp = 2,
|
||||||
@@ -63,58 +57,12 @@ local markerTypesMap = {
|
|||||||
Unknown43 = 43,
|
Unknown43 = 43,
|
||||||
}
|
}
|
||||||
|
|
||||||
---@alias MarkerType
|
|
||||||
---| "UpsideDownCone"
|
|
||||||
---| "VerticalCylinder"
|
|
||||||
---| "ThickChevronUp"
|
|
||||||
---| "ThinChevronUp"
|
|
||||||
---| "CheckeredFlagRect"
|
|
||||||
---| "CheckeredFlagCircle"
|
|
||||||
---| "VerticleCircle"
|
|
||||||
---| "PlaneModel"
|
|
||||||
---| "LostMCTransparent"
|
|
||||||
---| "LostMC"
|
|
||||||
---| "Number0"
|
|
||||||
---| "Number1"
|
|
||||||
---| "Number2"
|
|
||||||
---| "Number3"
|
|
||||||
---| "Number4"
|
|
||||||
---| "Number5"
|
|
||||||
---| "Number6"
|
|
||||||
---| "Number7"
|
|
||||||
---| "Number8"
|
|
||||||
---| "Number9"
|
|
||||||
---| "ChevronUpx1"
|
|
||||||
---| "ChevronUpx2"
|
|
||||||
---| "ChevronUpx3"
|
|
||||||
---| "HorizontalCircleFat"
|
|
||||||
---| "ReplayIcon"
|
|
||||||
---| "HorizontalCircleSkinny"
|
|
||||||
---| "HorizontalCircleSkinny_Arrow"
|
|
||||||
---| "HorizontalSplitArrowCircle"
|
|
||||||
---| "DebugSphere"
|
|
||||||
---| "DollarSign"
|
|
||||||
---| "HorizontalBars"
|
|
||||||
---| "WolfHead"
|
|
||||||
---| "QuestionMark"
|
|
||||||
---| "PlaneSymbol"
|
|
||||||
---| "HelicopterSymbol"
|
|
||||||
---| "BoatSymbol"
|
|
||||||
---| "CarSymbol"
|
|
||||||
---| "MotorcycleSymbol"
|
|
||||||
---| "BikeSymbol"
|
|
||||||
---| "TruckSymbol"
|
|
||||||
---| "ParachuteSymbol"
|
|
||||||
---| "Unknown41"
|
|
||||||
---| "SawbladeSymbol"
|
|
||||||
---| "Unknown43"
|
|
||||||
|
|
||||||
---@class MarkerProps
|
---@class MarkerProps
|
||||||
---@field type MarkerType | number
|
---@field type? MarkerType | integer
|
||||||
---@field coords { x: number, y: number, z: number }
|
---@field coords { x: number, y: number, z: number }
|
||||||
---@field width? number
|
---@field width? number
|
||||||
---@field height? number
|
---@field height? number
|
||||||
---@field color? { r: number, g: number, b: number, a: number }
|
---@field color? { r: integer, g: integer, b: integer, a: integer }
|
||||||
---@field rotation? { x: number, y: number, z: number }
|
---@field rotation? { x: number, y: number, z: number }
|
||||||
---@field direction? { x: number, y: number, z: number }
|
---@field direction? { x: number, y: number, z: number }
|
||||||
---@field bobUpAndDown? boolean
|
---@field bobUpAndDown? boolean
|
||||||
@@ -122,9 +70,25 @@ local markerTypesMap = {
|
|||||||
---@field rotate? boolean
|
---@field rotate? boolean
|
||||||
---@field textureDict? string
|
---@field textureDict? string
|
||||||
---@field textureName? string
|
---@field textureName? string
|
||||||
|
---@field invert? boolean
|
||||||
|
|
||||||
---@param self MarkerProps
|
local vector3_zero = vector3(0, 0, 0)
|
||||||
local function drawMarker(self)
|
|
||||||
|
local marker_mt = {
|
||||||
|
type = 0,
|
||||||
|
width = 2.,
|
||||||
|
height = 1.,
|
||||||
|
color = {r = 255, g = 100, b = 0, a = 100},
|
||||||
|
rotation = vector3_zero,
|
||||||
|
direction = vector3_zero,
|
||||||
|
bobUpAndDown = false,
|
||||||
|
faceCamera = false,
|
||||||
|
rotate = false,
|
||||||
|
invert = false,
|
||||||
|
}
|
||||||
|
marker_mt.__index = marker_mt
|
||||||
|
|
||||||
|
function marker_mt:draw()
|
||||||
DrawMarker(
|
DrawMarker(
|
||||||
self.type,
|
self.type,
|
||||||
self.coords.x, self.coords.y, self.coords.z,
|
self.coords.x, self.coords.y, self.coords.z,
|
||||||
@@ -132,40 +96,19 @@ local function drawMarker(self)
|
|||||||
self.rotation.x, self.rotation.y, self.rotation.z,
|
self.rotation.x, self.rotation.y, self.rotation.z,
|
||||||
self.width, self.width, self.height,
|
self.width, self.width, self.height,
|
||||||
self.color.r, self.color.g, self.color.b, self.color.a,
|
self.color.r, self.color.g, self.color.b, self.color.a,
|
||||||
self.bobUpAndDown, self.faceCamera, 2, self.rotate, self.textureDict, self.textureName, false)
|
self.bobUpAndDown, self.faceCamera, 2, self.rotate, self.textureDict, self.textureName, self.invert)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param options MarkerProps
|
---@param options MarkerProps
|
||||||
function lib.marker.new(options)
|
function lib.marker.new(options)
|
||||||
local markerType
|
options.type =
|
||||||
if type(options.type) == "string" then
|
type(options.type) == 'string' and markerTypes[options.type]
|
||||||
markerType = markerTypesMap[options.type]
|
or type(options.type) == 'number' and options.type or nil
|
||||||
if markerType == nil then
|
|
||||||
error(("unknown marker type '%s'"):format(options.type))
|
|
||||||
end
|
|
||||||
elseif type(options.type) == "number" then
|
|
||||||
markerType = options.type
|
|
||||||
else
|
|
||||||
error(("expected marker type to have type 'string' or 'number' (received %s)"):format(type(options.type)))
|
|
||||||
end
|
|
||||||
|
|
||||||
local self = {}
|
local self = setmetatable(options, marker_mt)
|
||||||
self.type = markerType
|
|
||||||
self.coords = options.coords
|
|
||||||
self.color = options.color or defaultColor
|
|
||||||
self.width = options.width or defaultSize.width
|
|
||||||
self.height = options.height or defaultSize.height
|
|
||||||
self.rotation = options.rotation or defaultRotation
|
|
||||||
self.direction = options.direction or defaultDirection
|
|
||||||
self.bobUpAndDown = type(options.bobUpAndDown) == 'boolean' and options.bobUpAndDown
|
|
||||||
self.faceCamera = type(options.faceCamera) ~= 'boolean' or options.faceCamera
|
|
||||||
self.rotate = type(options.rotate) == 'boolean' and options.rotate
|
|
||||||
self.textureDict = options.textureDict or defaultTextureDict
|
|
||||||
self.textureName = options.textureName or defaultTextureName
|
|
||||||
self.draw = drawMarker
|
|
||||||
|
|
||||||
self.width += 0.0
|
self.width += .0
|
||||||
self.height += 0.0
|
self.height += .0
|
||||||
|
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -39,10 +39,14 @@ local function removePoint(self)
|
|||||||
points[self.id] = nil
|
points[self.id] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function hasRemovePoint(entry)
|
||||||
|
return entry.remove == removePoint
|
||||||
|
end
|
||||||
|
|
||||||
CreateThread(function()
|
CreateThread(function()
|
||||||
while true do
|
while true do
|
||||||
local coords = GetEntityCoords(cache.ped)
|
local coords = GetEntityCoords(cache.ped)
|
||||||
local newPoints = lib.grid.getNearbyEntries(coords, function(entry) return entry.remove == removePoint end) --[[@as CPoint[] ]]
|
local newPoints = lib.grid.getNearbyEntries(coords, hasRemovePoint) --[[@as CPoint[] ]]
|
||||||
local cellX, cellY = lib.grid.getCellPosition(coords)
|
local cellX, cellY = lib.grid.getCellPosition(coords)
|
||||||
cache.coords = coords
|
cache.coords = coords
|
||||||
closestPoint = nil
|
closestPoint = nil
|
||||||
@@ -89,9 +93,11 @@ CreateThread(function()
|
|||||||
nearbyCount += 1
|
nearbyCount += 1
|
||||||
nearbyPoints[nearbyCount] = point
|
nearbyPoints[nearbyCount] = point
|
||||||
|
|
||||||
if point.onEnter and not point.inside then
|
if not point.inside then
|
||||||
point.inside = true
|
point.inside = true
|
||||||
point:onEnter()
|
if point.onEnter then
|
||||||
|
point:onEnter()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
elseif point.currentDistance then
|
elseif point.currentDistance then
|
||||||
if point.onExit then point:onExit() end
|
if point.onExit then point:onExit() end
|
||||||
|
|||||||
175
imports/selector/shared.lua
Normal file
175
imports/selector/shared.lua
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
---@alias OxSelectorItem {[1]: number, [2]: any}
|
||||||
|
---@alias OxSelectorSet OxSelectorItem[]
|
||||||
|
|
||||||
|
---@class OxSelector: OxClass
|
||||||
|
---@field private sets OxSelectorSet | table<string, OxSelectorItem[]>
|
||||||
|
---@field private totalWeights table<string, number>
|
||||||
|
local OxSelector = lib.class("OxSelector")
|
||||||
|
|
||||||
|
local DEFAULT_SET = 'default'
|
||||||
|
local deepClone = lib.table.deepclone
|
||||||
|
|
||||||
|
|
||||||
|
local function calculateTotalWeight(set)
|
||||||
|
local total = 0
|
||||||
|
for i = 1, #set do
|
||||||
|
local item = set[i]
|
||||||
|
assert(type(item) == "table", "Each OxSelectorItem must be a table")
|
||||||
|
local weight = item[1]
|
||||||
|
assert(type(weight) == "number" and weight >= 0, "weight must be 0 or more")
|
||||||
|
total += weight
|
||||||
|
end
|
||||||
|
return total
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---@param sets OxSelectorSet | table<string, OxSelectorItem[]>
|
||||||
|
function OxSelector:constructor(sets)
|
||||||
|
if type(sets) ~= "table" then
|
||||||
|
lib.print.error("Invalid sets provided to OxSelector constructor")
|
||||||
|
end
|
||||||
|
|
||||||
|
if lib.table.type(sets) == "array" then
|
||||||
|
sets = { [DEFAULT_SET] = sets }
|
||||||
|
end
|
||||||
|
|
||||||
|
self.private.totalWeights = {}
|
||||||
|
self.private.sets = {}
|
||||||
|
|
||||||
|
for setName, set in pairs(sets) do
|
||||||
|
assert(type(set) == "table" and lib.table.type(set) == "array", "Each set must be an array of OxSelectorItem")
|
||||||
|
assert(#set > 0, "Each set must contain at least one OxSelectorItem")
|
||||||
|
|
||||||
|
self.private.totalWeights[setName] = calculateTotalWeight(set)
|
||||||
|
end
|
||||||
|
|
||||||
|
self.private.sets = sets
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get a random non-weighted item from a specific set
|
||||||
|
---@param setName? string
|
||||||
|
---@return OxSelectorItem?
|
||||||
|
function OxSelector:getRandom(setName)
|
||||||
|
local set = (setName and self.private.sets[setName]) or self.private.sets[DEFAULT_SET]
|
||||||
|
if not set then return nil end
|
||||||
|
local item = set[math.random(#set)][2]
|
||||||
|
|
||||||
|
return type(item) == "table" and deepClone(item) or item
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get a random weighted item from a specific set
|
||||||
|
---@param setName? string
|
||||||
|
---@return OxSelectorItem?
|
||||||
|
function OxSelector:getRandomWeighted(setName)
|
||||||
|
local set = (setName and self.private.sets[setName]) or self.private.sets[DEFAULT_SET]
|
||||||
|
if not set then return nil end
|
||||||
|
|
||||||
|
local totalWeight = self.private.totalWeights[setName or DEFAULT_SET]
|
||||||
|
if totalWeight == 0 then return nil end
|
||||||
|
|
||||||
|
local randomWeight = math.random() * totalWeight
|
||||||
|
local cumulativeWeight = 0
|
||||||
|
|
||||||
|
for i = 1, #set do
|
||||||
|
cumulativeWeight = cumulativeWeight + set[i][1]
|
||||||
|
if randomWeight <= cumulativeWeight then
|
||||||
|
local item = set[i][2]
|
||||||
|
return type(item) == "table" and deepClone(item) or item
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
--- get multiple non-weighted random items from a specific set
|
||||||
|
---@param setName? string
|
||||||
|
---@param count number
|
||||||
|
---@return OxSelectorItem[]
|
||||||
|
function OxSelector:getRandomAmount(setName, count)
|
||||||
|
assert(type(count) == "number" and count > 0, "Count must be a positive number")
|
||||||
|
local items = {}
|
||||||
|
for _ = 1, count do
|
||||||
|
local item = self:getRandom(setName)
|
||||||
|
if item then
|
||||||
|
table.insert(items, item)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return items
|
||||||
|
end
|
||||||
|
|
||||||
|
--- get multiple weighted random items from a specific set
|
||||||
|
---@param setName? string
|
||||||
|
---@param count number
|
||||||
|
---@return OxSelectorItem[]
|
||||||
|
function OxSelector:getRandomWeightedAmount(setName, count)
|
||||||
|
assert(type(count) == "number" and count > 0, "Count must be a positive number")
|
||||||
|
local items = {}
|
||||||
|
for _ = 1, count do
|
||||||
|
local item = self:getRandomWeighted(setName)
|
||||||
|
if item then
|
||||||
|
table.insert(items, item)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return items
|
||||||
|
end
|
||||||
|
|
||||||
|
--- get all items from a specific set
|
||||||
|
---@param setName? string
|
||||||
|
---@return OxSelectorItem[]
|
||||||
|
function OxSelector:getSet(setName)
|
||||||
|
return deepClone((setName and self.private.sets[setName]) or self.private.sets[DEFAULT_SET])
|
||||||
|
end
|
||||||
|
|
||||||
|
--- get all sets
|
||||||
|
---@return table<string, OxSelectorItem[]>
|
||||||
|
function OxSelector:getAllSets()
|
||||||
|
return deepClone(self.private.sets)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- add a new set
|
||||||
|
---@param setName string
|
||||||
|
---@param items OxSelectorItem[]
|
||||||
|
function OxSelector:addSet(setName, items)
|
||||||
|
assert(type(setName) == "string", "setName must be a string")
|
||||||
|
|
||||||
|
if self.private.sets[setName] then
|
||||||
|
lib.print.error("Selector set '" .. setName .. "' already exists.")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
assert(type(items) == "table" and lib.table.type(items) == "array", "items must be an array")
|
||||||
|
assert(#items > 0, "set must contain at least one OxSelectorItem")
|
||||||
|
|
||||||
|
self.private.totalWeights[setName] = calculateTotalWeight(items)
|
||||||
|
self.private.sets[setName] = items
|
||||||
|
end
|
||||||
|
|
||||||
|
--- update an existing set
|
||||||
|
---@param setName string
|
||||||
|
---@param newItems OxSelectorItem[]
|
||||||
|
function OxSelector:updateSet(setName, newItems)
|
||||||
|
assert(type(setName) == "string", "setName must be a string")
|
||||||
|
|
||||||
|
if not self.private.sets[setName] then
|
||||||
|
lib.print.error("Selector set '" .. setName .. "' does not exist.")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
assert(type(newItems) == "table" and lib.table.type(newItems) == "array", "newItems must be an array")
|
||||||
|
assert(#newItems > 0, "set must contain at least one OxSelectorItem")
|
||||||
|
|
||||||
|
self.private.totalWeights[setName] = calculateTotalWeight(newItems)
|
||||||
|
self.private.sets[setName] = newItems
|
||||||
|
end
|
||||||
|
|
||||||
|
--- remove a set
|
||||||
|
---@param setName string
|
||||||
|
function OxSelector:removeSet(setName)
|
||||||
|
assert(type(setName) == "string", "setName must be a string")
|
||||||
|
|
||||||
|
self.private.totalWeights[setName] = nil
|
||||||
|
self.private.sets[setName] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
lib.selector = OxSelector
|
||||||
|
return lib.selector
|
||||||
@@ -47,13 +47,10 @@ end
|
|||||||
---@return boolean
|
---@return boolean
|
||||||
---Compares if two values are equal, iterating over tables and matching both keys and values.
|
---Compares if two values are equal, iterating over tables and matching both keys and values.
|
||||||
local function table_matches(t1, t2)
|
local function table_matches(t1, t2)
|
||||||
local tabletype1 = table.type(t1)
|
local type1, type2 = type(t1), type(t2)
|
||||||
|
|
||||||
if not tabletype1 then return t1 == t2 end
|
if type1 ~= type2 then return false end
|
||||||
|
if type1 ~= 'table' and type2 ~= 'table' then return t1 == t2 end
|
||||||
if tabletype1 ~= table.type(t2) or (tabletype1 == 'array' and #t1 ~= #t2) then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
for k, v1 in pairs(t1) do
|
for k, v1 in pairs(t1) do
|
||||||
local v2 = t2[k]
|
local v2 = t2[k]
|
||||||
@@ -123,11 +120,24 @@ local function shuffle(tbl)
|
|||||||
return tbl
|
return tbl
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---@param tbl table
|
||||||
|
---@param fn function(value: any, key: any): any
|
||||||
|
---@return table
|
||||||
|
local function map(tbl, fn)
|
||||||
|
local result = {}
|
||||||
|
for k, v in pairs(tbl) do
|
||||||
|
result[k] = fn(v, k)
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
table.contains = contains
|
table.contains = contains
|
||||||
table.matches = table_matches
|
table.matches = table_matches
|
||||||
table.deepclone = table_deepclone
|
table.deepclone = table_deepclone
|
||||||
table.merge = table_merge
|
table.merge = table_merge
|
||||||
table.shuffle = shuffle
|
table.shuffle = shuffle
|
||||||
|
table.map = map
|
||||||
|
|
||||||
local frozenNewIndex = function(self) error(('cannot set values on a frozen table (%s)'):format(self), 2) end
|
local frozenNewIndex = function(self) error(('cannot set values on a frozen table (%s)'):format(self), 2) end
|
||||||
local _rawset = rawset
|
local _rawset = rawset
|
||||||
|
|||||||
@@ -122,8 +122,15 @@ local function removeZone(zone)
|
|||||||
|
|
||||||
insideZones[zone.id] = nil
|
insideZones[zone.id] = nil
|
||||||
|
|
||||||
table.remove(exitingZones, exitingZones:indexOf(zone))
|
local exitingIndex = exitingZones:indexOf(zone)
|
||||||
table.remove(enteringZones, enteringZones:indexOf(zone))
|
if exitingIndex then
|
||||||
|
table.remove(exitingZones, exitingIndex)
|
||||||
|
end
|
||||||
|
|
||||||
|
local enteringIndex = enteringZones:indexOf(zone)
|
||||||
|
if enteringIndex then
|
||||||
|
table.remove(enteringZones, enteringIndex)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
CreateThread(function()
|
CreateThread(function()
|
||||||
@@ -338,6 +345,8 @@ local function setZone(data)
|
|||||||
data.contains = data.contains or contains
|
data.contains = data.contains or contains
|
||||||
|
|
||||||
if lib.context == 'client' then
|
if lib.context == 'client' then
|
||||||
|
local coords = cache.coords or GetEntityCoords(cache.ped)
|
||||||
|
data.distance = #(data.coords - coords)
|
||||||
data.setDebug = setDebug
|
data.setDebug = setDebug
|
||||||
|
|
||||||
if data.debug then
|
if data.debug then
|
||||||
|
|||||||
@@ -7,27 +7,27 @@
|
|||||||
"confirm": "Confirmer",
|
"confirm": "Confirmer",
|
||||||
"more": "Plus...",
|
"more": "Plus...",
|
||||||
"settings": {
|
"settings": {
|
||||||
"locale": "Change locale",
|
"locale": "Changer la langue",
|
||||||
"locale_description": "Current language: ${language} (%s)",
|
"locale_description": "Langue actuelle : ${language} (%s)",
|
||||||
"notification_audio": "Notification audio",
|
"notification_audio": "Audio des notifications",
|
||||||
"notification_position": "Notification position"
|
"notification_position": "Position des notifications"
|
||||||
},
|
},
|
||||||
"position": {
|
"position": {
|
||||||
"bottom": "Bottom",
|
"bottom": "Bas",
|
||||||
"bottom-left": "Bottom-left",
|
"bottom-left": "Bas-gauche",
|
||||||
"bottom-right": "Bottom-right",
|
"bottom-right": "Bas-droite",
|
||||||
"center-left": "Center-left",
|
"center-left": "Centre-gauche",
|
||||||
"center-right": "Center-right",
|
"center-right": "Centre-droite",
|
||||||
"top": "Top",
|
"top": "Haut",
|
||||||
"top-left": "Top-left",
|
"top-left": "Haut-gauche",
|
||||||
"top-right": "Top-right"
|
"top-right": "Haut-droite"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"open_radial_menu": "Open radial menu",
|
"open_radial_menu": "Ouvrir le menu radial",
|
||||||
"cancel_progress": "Cancel current progress bar",
|
"cancel_progress": "Annuler la barre de progression actuelle",
|
||||||
"txadmin_announcement": "Server announcement by %s",
|
"txadmin_announcement": "Annonce serveur par %s",
|
||||||
"txadmin_dm": "Direct Message from %s",
|
"txadmin_dm": "Message privé de %s",
|
||||||
"txadmin_warn": "You have been warned by %s",
|
"txadmin_warn": "Vous avez été averti par %s",
|
||||||
"txadmin_warn_content": "%s \nAction ID: %s",
|
"txadmin_warn_content": "%s\nID de l'action : %s",
|
||||||
"txadmin_scheduledrestart": "Scheduled Restart"
|
"txadmin_scheduledrestart": "Redémarrage programmé"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,25 +9,25 @@
|
|||||||
"settings": {
|
"settings": {
|
||||||
"locale": "Endre språk",
|
"locale": "Endre språk",
|
||||||
"locale_description": "Nåværende språk: ${language} (%s)",
|
"locale_description": "Nåværende språk: ${language} (%s)",
|
||||||
"notification_audio": "Varslings lyd",
|
"notification_audio": "Varslingslyd",
|
||||||
"notification_position": "Varslings posisjon"
|
"notification_position": "Varslingsposisjon"
|
||||||
},
|
},
|
||||||
"position": {
|
"position": {
|
||||||
"bottom": "Bunn",
|
"bottom": "Nederst",
|
||||||
"bottom-left": "Bunn-venstre",
|
"bottom-left": "Nederst til venstre",
|
||||||
"bottom-right": "Bunn-høyre",
|
"bottom-right": "Nederst til høyre",
|
||||||
"center-left": "Senter-venstre",
|
"center-left": "Midten venstre",
|
||||||
"center-right": "Senter-høyre",
|
"center-right": "Midten høyre",
|
||||||
"top": "Top",
|
"top": "Øverst",
|
||||||
"top-left": "Top-venstre",
|
"top-left": "Øverst til venstre",
|
||||||
"top-right": "Top-høyre"
|
"top-right": "Øverst til høyre"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"open_radial_menu": "Åpne radial menyen",
|
"open_radial_menu": "Åpne radialmenyen",
|
||||||
"cancel_progress": "Avbryt den nåværende progresjonsbaren",
|
"cancel_progress": "Avbryt den nåværende progresjonsbaren",
|
||||||
"txadmin_announcement": "Server annonsering fra %s",
|
"txadmin_announcement": "Serverannonsering fra %s",
|
||||||
"txadmin_dm": "Direktemelding fra %s",
|
"txadmin_dm": "Direktemelding fra %s",
|
||||||
"txadmin_warn": "Du har fått en advarsel fra %s",
|
"txadmin_warn": "Du har fått en advarsel fra %s",
|
||||||
"txadmin_warn_content": "%s \nAction ID: %s",
|
"txadmin_warn_content": "%s \nAction ID: %s",
|
||||||
"txadmin_scheduledrestart": "Planlagt Omstart"
|
"txadmin_scheduledrestart": "Planlagt omstart"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "@communityox/ox_lib",
|
"name": "@communityox/ox_lib",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nativewrappers/client": "^1.7.33",
|
"@nativewrappers/fivem": "^0.0.103",
|
||||||
"csstype": "^3.1.3",
|
"csstype": "^3.1.3",
|
||||||
"fast-printf": "^1.6.9",
|
"fast-printf": "^1.6.9",
|
||||||
"typescript": "^5.4.2",
|
"typescript": "^5.4.2",
|
||||||
@@ -97,7 +97,7 @@
|
|||||||
|
|
||||||
"@mantine/utils": ["@mantine/utils@6.0.22", "", { "peerDependencies": { "react": ">=16.8.0" } }, "sha512-RSKlNZvxhMCkOFZ6slbYvZYbWjHUM+PxDQnupIOxIdsTZQQjx/BFfrfJ7kQFOP+g7MtpOds8weAetEs5obwMOQ=="],
|
"@mantine/utils": ["@mantine/utils@6.0.22", "", { "peerDependencies": { "react": ">=16.8.0" } }, "sha512-RSKlNZvxhMCkOFZ6slbYvZYbWjHUM+PxDQnupIOxIdsTZQQjx/BFfrfJ7kQFOP+g7MtpOds8weAetEs5obwMOQ=="],
|
||||||
|
|
||||||
"@nativewrappers/client": ["@nativewrappers/client@1.7.33", "", {}, "sha512-phuBBGdDPxZiZyw5CaFs1XWfvllnEtwATMdLaNucwMofVg/O/FjlP1bTUq4SOm4qhSZ4Zdo351ijHzBSIbZs6g=="],
|
"@nativewrappers/fivem": ["@nativewrappers/fivem@0.0.103", "", {}, "sha512-x0W00Mx9ZN/rTS9XZc5Kf1hjahqRmlo9sPiuJP4kCYeQG4LDJyglXCsHcfNfygGq6WEblG1W2FLgm4MGDn/wHA=="],
|
||||||
|
|
||||||
"@radix-ui/number": ["@radix-ui/number@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10" } }, "sha512-Ofwh/1HX69ZfJRiRBMTy7rgjAzHmwe4kW9C9Y99HTRUcYLUuVT0KESFj15rPjRgKJs20GPq8Bm5aEDJ8DuA3vA=="],
|
"@radix-ui/number": ["@radix-ui/number@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10" } }, "sha512-Ofwh/1HX69ZfJRiRBMTy7rgjAzHmwe4kW9C9Y99HTRUcYLUuVT0KESFj15rPjRgKJs20GPq8Bm5aEDJ8DuA3vA=="],
|
||||||
|
|
||||||
|
|||||||
120
package/client/resource/addKeybind/index.ts
Normal file
120
package/client/resource/addKeybind/index.ts
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
interface CKeybind extends KeybindProps {
|
||||||
|
currentKey: string;
|
||||||
|
disabled: boolean;
|
||||||
|
isPressed: boolean;
|
||||||
|
hash: number;
|
||||||
|
getCurrentKey(): string;
|
||||||
|
isControlPressed(): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface KeybindProps {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
defaultMapper?: string;
|
||||||
|
defaultKey?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
disable?(this: CKeybind, toggle: boolean): void;
|
||||||
|
onPressed?(this: CKeybind): void;
|
||||||
|
onReleased?(this: CKeybind): void;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keybinds: Record<string, CKeybind> = {};
|
||||||
|
|
||||||
|
class Keybind implements CKeybind {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
defaultMapper?: string;
|
||||||
|
defaultKey?: string;
|
||||||
|
onPressed?: (this: CKeybind) => void;
|
||||||
|
onReleased?: (this: CKeybind) => void;
|
||||||
|
secondaryKey?: string;
|
||||||
|
secondaryMapper?: string;
|
||||||
|
[key: string]: any;
|
||||||
|
|
||||||
|
disabled: boolean = false;
|
||||||
|
isPressed: boolean = false;
|
||||||
|
hash: number;
|
||||||
|
|
||||||
|
constructor(data: KeybindProps) {
|
||||||
|
this.name = data.name;
|
||||||
|
this.description = data.description;
|
||||||
|
this.defaultMapper = data.defaultMapper ?? "keyboard";
|
||||||
|
this.defaultKey = data.defaultKey ?? "";
|
||||||
|
this.secondaryKey = data.secondaryKey;
|
||||||
|
this.secondaryMapper = data.secondaryMapper;
|
||||||
|
|
||||||
|
if (typeof data.disabled === "boolean") this.disabled = data.disabled;
|
||||||
|
this.onPressed = data.onPressed;
|
||||||
|
this.onReleased = data.onReleased;
|
||||||
|
|
||||||
|
this.hash = GetHashKey("+" + this.name) | 0x80000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
get currentKey(): string {
|
||||||
|
return this.getCurrentKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentKey(): string {
|
||||||
|
const label = GetControlInstructionalButton(0, this.hash, true);
|
||||||
|
return label.substring(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
isControlPressed(): boolean {
|
||||||
|
return this.isPressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
disable(toggle: boolean): void {
|
||||||
|
this.disabled = toggle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function addKeybind(data: KeybindProps): CKeybind {
|
||||||
|
const kb = new Keybind(data);
|
||||||
|
|
||||||
|
keybinds[kb.name] = kb;
|
||||||
|
|
||||||
|
RegisterCommand("+" + kb.name, () => {
|
||||||
|
if (kb.disabled || IsPauseMenuActive()) return;
|
||||||
|
kb.isPressed = true;
|
||||||
|
kb.onPressed?.call(kb);
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
RegisterCommand("-" + kb.name, () => {
|
||||||
|
if (kb.disabled || IsPauseMenuActive()) return;
|
||||||
|
kb.isPressed = false;
|
||||||
|
kb.onReleased?.call(kb);
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
RegisterKeyMapping(
|
||||||
|
"+" + kb.name,
|
||||||
|
kb.description,
|
||||||
|
kb.defaultMapper ?? "keyboard",
|
||||||
|
kb.defaultKey ?? ""
|
||||||
|
);
|
||||||
|
|
||||||
|
if (kb.secondaryKey) {
|
||||||
|
RegisterKeyMapping(
|
||||||
|
"~!+" + kb.name,
|
||||||
|
kb.description,
|
||||||
|
kb.secondaryMapper ?? kb.defaultMapper ?? "keyboard",
|
||||||
|
kb.secondaryKey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
emit("chat:removeSuggestion", `/+${kb.name}`);
|
||||||
|
emit("chat:removeSuggestion", `/-${kb.name}`);
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
return kb;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getKeybind(name: string): CKeybind | undefined {
|
||||||
|
return keybinds[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAllKeybinds(): Readonly<Record<string, CKeybind>> {
|
||||||
|
return keybinds;
|
||||||
|
}
|
||||||
@@ -47,7 +47,7 @@ export function triggerServerCallback<T = unknown>(
|
|||||||
|
|
||||||
return new Promise<T>((resolve, reject) => {
|
return new Promise<T>((resolve, reject) => {
|
||||||
pendingCallbacks[key] = (args) => {
|
pendingCallbacks[key] = (args) => {
|
||||||
if (args[0] === 'cb_invalid') reject(`callback '${eventName} does not exist`);
|
if (Array.isArray(args) && args[0] === 'cb_invalid') reject(`callback '${eventName} does not exist`);
|
||||||
|
|
||||||
resolve(args);
|
resolve(args);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,3 +14,4 @@ export * from './vehicleProperties';
|
|||||||
export * from './callback';
|
export * from './callback';
|
||||||
export * from './points';
|
export * from './points';
|
||||||
export * from './dui';
|
export * from './dui';
|
||||||
|
export * from './addKeybind';
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ type NotificationPosition =
|
|||||||
| 'bottom-left'
|
| 'bottom-left'
|
||||||
| 'center-right'
|
| 'center-right'
|
||||||
| 'center-left';
|
| 'center-left';
|
||||||
type NotificationType = 'inform' | 'error' | 'success';
|
type NotificationType = 'inform' | 'error' | 'success' | 'warning';
|
||||||
type IconAnimation = 'spin' | 'spinPulse' | 'spinReverse' | 'pulse' | 'beat' | 'fade' | 'beatFade' | 'bounce' | 'shake';
|
type IconAnimation = 'spin' | 'spinPulse' | 'spinReverse' | 'pulse' | 'beat' | 'fade' | 'beatFade' | 'bounce' | 'shake';
|
||||||
|
|
||||||
interface NotifyProps {
|
interface NotifyProps {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ interface PropProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface ProgressProps {
|
interface ProgressProps {
|
||||||
|
label?: string;
|
||||||
duration: number;
|
duration: number;
|
||||||
position?: 'middle' | 'bottom';
|
position?: 'middle' | 'bottom';
|
||||||
useWhileDead?: boolean;
|
useWhileDead?: boolean;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Vector3 } from '@nativewrappers/client';
|
import { Vector3 } from '@nativewrappers/fivem';
|
||||||
import { cache } from '../cache';
|
import { cache } from '../cache';
|
||||||
|
|
||||||
let points: Point[] = [];
|
let points: Point[] = [];
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "@communityox/ox_lib",
|
"name": "@communityox/ox_lib",
|
||||||
"author": "Overextended",
|
"author": "Overextended",
|
||||||
"version": "3.30.7",
|
"version": "3.32.3",
|
||||||
"description": "JS/TS wrapper for ox_lib exports",
|
"description": "JS/TS wrapper for ox_lib exports",
|
||||||
"main": "./shared/index.js",
|
"main": "./shared/index.js",
|
||||||
"types": "./shared/index.d.ts",
|
"types": "./shared/index.d.ts",
|
||||||
"sideEffects": ["./shared/resource/locale/index.js"],
|
"sideEffects": [
|
||||||
|
"./shared/resource/locale/index.js"
|
||||||
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"compile": "tsc",
|
"compile": "tsc",
|
||||||
"prepare": "npm run compile"
|
"prepare": "npm run compile"
|
||||||
@@ -19,14 +21,14 @@
|
|||||||
],
|
],
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/communityox/ox_lib.git"
|
"url": "git+https://github.com/CommunityOx/ox_lib.git"
|
||||||
},
|
},
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/communityox/ox_lib/issues"
|
"url": "https://github.com/CommunityOx/ox_lib/issues"
|
||||||
},
|
},
|
||||||
"license": "LGPL-3.0",
|
"license": "LGPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nativewrappers/client": "^1.7.33",
|
"@nativewrappers/fivem": "^0.0.103",
|
||||||
"csstype": "^3.1.3",
|
"csstype": "^3.1.3",
|
||||||
"fast-printf": "^1.6.9",
|
"fast-printf": "^1.6.9",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
@@ -40,4 +42,4 @@
|
|||||||
"@types/react": "^18.2.66",
|
"@types/react": "^18.2.66",
|
||||||
"prettier": "^2.8.8"
|
"prettier": "^2.8.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,7 @@ export function triggerClientCallback<T = unknown>(
|
|||||||
|
|
||||||
return new Promise<T>((resolve, reject) => {
|
return new Promise<T>((resolve, reject) => {
|
||||||
pendingCallbacks[key] = (args) => {
|
pendingCallbacks[key] = (args) => {
|
||||||
if (args[0] === 'cb_invalid') reject(`callback '${eventName} does not exist`);
|
if (Array.isArray(args) && args[0] === 'cb_invalid') reject(`callback '${eventName} does not exist`);
|
||||||
|
|
||||||
resolve(args);
|
resolve(args);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
export * from './resource';
|
export * from './resource';
|
||||||
|
|
||||||
|
export const context: 'server' | 'client' = IsDuplicityVersion() ? 'server' : 'client';
|
||||||
|
|
||||||
// https://www.raygesualdo.com/posts/flattening-object-keys-with-typescript-types/
|
// https://www.raygesualdo.com/posts/flattening-object-keys-with-typescript-types/
|
||||||
export type FlattenObjectKeys<T extends Record<string, any>, Key = keyof T> = Key extends string
|
export type FlattenObjectKeys<T extends Record<string, any>, Key = keyof T> = Key extends string
|
||||||
? T[Key] extends Record<string, unknown>
|
? T[Key] extends Record<string, unknown>
|
||||||
|
|||||||
29
package/shared/resource/getNearbyVehicles/index.ts
Normal file
29
package/shared/resource/getNearbyVehicles/index.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { cache } from '../cache/index';
|
||||||
|
import { context } from '../../index';
|
||||||
|
import { Vector3 } from "@nativewrappers/fivem";
|
||||||
|
|
||||||
|
interface NearbyVehicle {
|
||||||
|
vehicle: number;
|
||||||
|
coords: Vector3;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getNearbyVehicles(coords: Vector3, maxDistance: number = 2.0, includePlayerVehicle: boolean = false): NearbyVehicle[] {
|
||||||
|
const vehicles = GetGamePool('CVehicle');
|
||||||
|
const nearbyVehicles: NearbyVehicle[] = [];
|
||||||
|
|
||||||
|
for (const vehicle of vehicles) {
|
||||||
|
if (context === 'server' || !cache.vehicle || vehicle !== cache.vehicle || includePlayerVehicle) {
|
||||||
|
const vehicleCoords = Vector3.fromArray(GetEntityCoords(vehicle, true));
|
||||||
|
const distance = vehicleCoords.distance(coords);
|
||||||
|
|
||||||
|
if (distance < maxDistance && NetworkGetEntityIsNetworked(vehicle) ) {
|
||||||
|
nearbyVehicles.push({
|
||||||
|
vehicle,
|
||||||
|
coords: vehicleCoords
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nearbyVehicles;
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
export * from './version';
|
export * from './version';
|
||||||
export * from './cache';
|
export * from './cache';
|
||||||
export * from './locale'
|
export * from './locale';
|
||||||
|
export * from './getNearbyVehicles';
|
||||||
|
|||||||
55
resource/cache/client.lua
vendored
55
resource/cache/client.lua
vendored
@@ -29,40 +29,43 @@ local GetCurrentPedWeapon = GetCurrentPedWeapon
|
|||||||
CreateThread(function()
|
CreateThread(function()
|
||||||
while true do
|
while true do
|
||||||
local ped = PlayerPedId()
|
local ped = PlayerPedId()
|
||||||
cache:set('ped', ped)
|
|
||||||
|
|
||||||
local vehicle = GetVehiclePedIsIn(ped, false)
|
-- If the player's ped is changed, the ped value may return 0 during the transition. If the value is 0, skip all checks.
|
||||||
|
if ped ~= 0 then
|
||||||
|
cache:set('ped', ped)
|
||||||
|
|
||||||
if vehicle > 0 then
|
local vehicle = GetVehiclePedIsIn(ped, false)
|
||||||
if vehicle ~= cache.vehicle then
|
|
||||||
|
if vehicle > 0 then
|
||||||
|
if vehicle ~= cache.vehicle then
|
||||||
|
cache:set('seat', false)
|
||||||
|
end
|
||||||
|
|
||||||
|
cache:set('vehicle', vehicle)
|
||||||
|
|
||||||
|
if not cache.seat or GetPedInVehicleSeat(vehicle, cache.seat) ~= ped then
|
||||||
|
for i = -1, GetVehicleMaxNumberOfPassengers(vehicle) - 1 do
|
||||||
|
if GetPedInVehicleSeat(vehicle, i) == ped then
|
||||||
|
cache:set('seat', i)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
cache:set('vehicle', false)
|
||||||
cache:set('seat', false)
|
cache:set('seat', false)
|
||||||
end
|
end
|
||||||
|
|
||||||
cache:set('vehicle', vehicle)
|
if cache.game == 'redm' then
|
||||||
|
local mount = GetMount(ped)
|
||||||
if not cache.seat or GetPedInVehicleSeat(vehicle, cache.seat) ~= ped then
|
local onMount = IsPedOnMount(ped)
|
||||||
for i = -1, GetVehicleMaxNumberOfPassengers(vehicle) - 1 do
|
cache:set('mount', onMount and mount or false)
|
||||||
if GetPedInVehicleSeat(vehicle, i) == ped then
|
|
||||||
cache:set('seat', i)
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
else
|
|
||||||
cache:set('vehicle', false)
|
local hasWeapon, currentWeapon = GetCurrentPedWeapon(ped, true)
|
||||||
cache:set('seat', false)
|
cache:set('weapon', (hasWeapon and currentWeapon ~= 0) and currentWeapon or false)
|
||||||
end
|
end
|
||||||
|
|
||||||
if cache.game == 'redm' then
|
|
||||||
local mount = GetMount(ped)
|
|
||||||
local onMount = IsPedOnMount(ped)
|
|
||||||
cache:set('mount', onMount and mount or false)
|
|
||||||
end
|
|
||||||
|
|
||||||
local hasWeapon, currentWeapon = GetCurrentPedWeapon(ped, true)
|
|
||||||
|
|
||||||
cache:set('weapon', hasWeapon and currentWeapon or false)
|
|
||||||
|
|
||||||
Wait(100)
|
Wait(100)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|||||||
@@ -30,9 +30,12 @@ local input
|
|||||||
---@field searchable? boolean
|
---@field searchable? boolean
|
||||||
---@field description? string
|
---@field description? string
|
||||||
---@field maxSelectedValues? number
|
---@field maxSelectedValues? number
|
||||||
|
---@field minLength? number
|
||||||
|
---@field maxLength? number
|
||||||
|
|
||||||
---@class InputDialogOptionsProps
|
---@class InputDialogOptionsProps
|
||||||
---@field allowCancel? boolean
|
---@field allowCancel? boolean
|
||||||
|
---@field size? 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
||||||
|
|
||||||
---@param heading string
|
---@param heading string
|
||||||
---@param rows string[] | InputDialogRowProps[]
|
---@param rows string[] | InputDialogRowProps[]
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ local openMenu
|
|||||||
|
|
||||||
---@alias MenuPosition 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
|
---@alias MenuPosition 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
|
||||||
---@alias MenuChangeFunction fun(selected: number, scrollIndex?: number, args?: any, checked?: boolean)
|
---@alias MenuChangeFunction fun(selected: number, scrollIndex?: number, args?: any, checked?: boolean)
|
||||||
|
---@alias MenuScrollSelectChangeFunction fun(selected: number, scrollIndex?: number, args?: any)
|
||||||
|
|
||||||
---@class MenuOptions
|
---@class MenuOptions
|
||||||
---@field label string
|
---@field label string
|
||||||
@@ -35,9 +36,9 @@ local openMenu
|
|||||||
---@field disableInput? boolean
|
---@field disableInput? boolean
|
||||||
---@field canClose? boolean
|
---@field canClose? boolean
|
||||||
---@field onClose? fun(keyPressed?: 'Escape' | 'Backspace')
|
---@field onClose? fun(keyPressed?: 'Escape' | 'Backspace')
|
||||||
---@field onSelected? MenuChangeFunction
|
---@field onSelected? MenuScrollSelectChangeFunction
|
||||||
---@field onSideScroll? MenuChangeFunction
|
---@field onSideScroll? MenuScrollSelectChangeFunction
|
||||||
---@field onCheck? MenuChangeFunction
|
---@field onCheck? fun(selected: number, checked: boolean, args?: any)
|
||||||
---@field cb? MenuChangeFunction
|
---@field cb? MenuChangeFunction
|
||||||
|
|
||||||
---@param data MenuProps
|
---@param data MenuProps
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ local DisableControlAction = DisableControlAction
|
|||||||
local DisablePlayerFiring = DisablePlayerFiring
|
local DisablePlayerFiring = DisablePlayerFiring
|
||||||
local playerState = LocalPlayer.state
|
local playerState = LocalPlayer.state
|
||||||
local createdProps = {}
|
local createdProps = {}
|
||||||
|
local maxProps = GetConvarInt('ox:progressPropLimit', 2)
|
||||||
|
|
||||||
---@class ProgressPropProps
|
---@class ProgressPropProps
|
||||||
---@field model string
|
---@field model string
|
||||||
@@ -34,13 +35,16 @@ local createdProps = {}
|
|||||||
---@field disable? { move?: boolean, sprint?: boolean, car?: boolean, combat?: boolean, mouse?: boolean }
|
---@field disable? { move?: boolean, sprint?: boolean, car?: boolean, combat?: boolean, mouse?: boolean }
|
||||||
|
|
||||||
local function createProp(ped, prop)
|
local function createProp(ped, prop)
|
||||||
lib.requestModel(prop.model)
|
local ok, result = pcall(lib.requestModel, prop.model)
|
||||||
|
|
||||||
|
if not ok then return lib.print.error(result) end
|
||||||
|
|
||||||
local coords = GetEntityCoords(ped)
|
local coords = GetEntityCoords(ped)
|
||||||
local object = CreateObject(prop.model, coords.x, coords.y, coords.z, false, false, false)
|
local object = CreateObject(result, coords.x, coords.y, coords.z, false, false, false)
|
||||||
|
|
||||||
AttachEntityToEntity(object, ped, GetPedBoneIndex(ped, prop.bone or 60309), prop.pos.x, prop.pos.y, prop.pos.z, prop.rot.x, prop.rot.y, prop.rot.z, true,
|
AttachEntityToEntity(object, ped, GetPedBoneIndex(ped, prop.bone or 60309), prop.pos.x, prop.pos.y, prop.pos.z, prop.rot.x, prop.rot.y, prop.rot.z, true,
|
||||||
true, false, true, prop.rotOrder or 0, true)
|
true, false, true, prop.rotOrder or 0, true)
|
||||||
SetModelAsNoLongerNeeded(prop.model)
|
SetModelAsNoLongerNeeded(result)
|
||||||
|
|
||||||
return object
|
return object
|
||||||
end
|
end
|
||||||
@@ -90,7 +94,7 @@ local function startProgress(data)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if data.prop then
|
if data.prop then
|
||||||
playerState:set('lib:progressProps', data.prop, true)
|
TriggerServerEvent('ox_lib:progressProps', data.prop)
|
||||||
end
|
end
|
||||||
|
|
||||||
local disable = data.disable
|
local disable = data.disable
|
||||||
@@ -137,7 +141,7 @@ local function startProgress(data)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if data.prop then
|
if data.prop then
|
||||||
playerState:set('lib:progressProps', nil, true)
|
TriggerServerEvent('ox_lib:progressProps', nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
if anim then
|
if anim then
|
||||||
@@ -225,14 +229,18 @@ end
|
|||||||
|
|
||||||
local function deleteProgressProps(serverId)
|
local function deleteProgressProps(serverId)
|
||||||
local playerProps = createdProps[serverId]
|
local playerProps = createdProps[serverId]
|
||||||
|
|
||||||
if not playerProps then return end
|
if not playerProps then return end
|
||||||
|
|
||||||
|
createdProps[serverId] = nil
|
||||||
|
|
||||||
for i = 1, #playerProps do
|
for i = 1, #playerProps do
|
||||||
local prop = playerProps[i]
|
local prop = playerProps[i]
|
||||||
|
|
||||||
if DoesEntityExist(prop) then
|
if DoesEntityExist(prop) then
|
||||||
DeleteEntity(prop)
|
DeleteEntity(prop)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
createdProps[serverId] = nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
RegisterNetEvent('onPlayerDropped', function(serverId)
|
RegisterNetEvent('onPlayerDropped', function(serverId)
|
||||||
@@ -248,22 +256,29 @@ AddStateBagChangeHandler('lib:progressProps', nil, function(bagName, key, value,
|
|||||||
local ped = GetPlayerPed(ply)
|
local ped = GetPlayerPed(ply)
|
||||||
local serverId = GetPlayerServerId(ply)
|
local serverId = GetPlayerServerId(ply)
|
||||||
|
|
||||||
if not value then
|
if not value or createdProps[serverId] then
|
||||||
return deleteProgressProps(serverId)
|
return deleteProgressProps(serverId)
|
||||||
end
|
end
|
||||||
|
|
||||||
createdProps[serverId] = {}
|
local playerProps = {}
|
||||||
local playerProps = createdProps[serverId]
|
|
||||||
|
|
||||||
if value.model then
|
if value.model then
|
||||||
playerProps[#playerProps + 1] = createProp(ped, value)
|
local prop = createProp(ped, value)
|
||||||
|
|
||||||
|
if prop then
|
||||||
|
playerProps[#playerProps + 1] = prop
|
||||||
|
end
|
||||||
else
|
else
|
||||||
for i = 1, #value do
|
local propCount = math.min(maxProps, #value)
|
||||||
local prop = value[i]
|
|
||||||
|
for i = 1, propCount do
|
||||||
|
local prop = createProp(ped, value[i])
|
||||||
|
|
||||||
if prop then
|
if prop then
|
||||||
playerProps[#playerProps + 1] = createProp(ped, prop)
|
playerProps[#playerProps + 1] = prop
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
createdProps[serverId] = playerProps
|
||||||
end)
|
end)
|
||||||
|
|||||||
12
resource/interface/server/progress.lua
Normal file
12
resource/interface/server/progress.lua
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
local maxProps = GetConvarInt('ox:progressPropLimit', 2)
|
||||||
|
|
||||||
|
---@param props ProgressPropProps | ProgressPropProps[] | nil
|
||||||
|
RegisterNetEvent('ox_lib:progressProps', function(props)
|
||||||
|
if type(props) == 'table' then
|
||||||
|
props = #props > maxProps and { table.unpack(props, 1, maxProps) } or props
|
||||||
|
else
|
||||||
|
props = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
Player(source).state:set('lib:progressProps', props, true)
|
||||||
|
end)
|
||||||
@@ -12,6 +12,7 @@ if cache.game == 'redm' then return end
|
|||||||
---@field model? number
|
---@field model? number
|
||||||
---@field plate? string
|
---@field plate? string
|
||||||
---@field plateIndex? number
|
---@field plateIndex? number
|
||||||
|
---@field lockState? number
|
||||||
---@field bodyHealth? number
|
---@field bodyHealth? number
|
||||||
---@field engineHealth? number
|
---@field engineHealth? number
|
||||||
---@field tankHealth? number
|
---@field tankHealth? number
|
||||||
@@ -202,6 +203,7 @@ function lib.getVehicleProperties(vehicle)
|
|||||||
model = GetEntityModel(vehicle),
|
model = GetEntityModel(vehicle),
|
||||||
plate = GetVehicleNumberPlateText(vehicle),
|
plate = GetVehicleNumberPlateText(vehicle),
|
||||||
plateIndex = GetVehicleNumberPlateTextIndex(vehicle),
|
plateIndex = GetVehicleNumberPlateTextIndex(vehicle),
|
||||||
|
lockState = GetVehicleDoorLockStatus(vehicle),
|
||||||
bodyHealth = math.floor(GetVehicleBodyHealth(vehicle) + 0.5),
|
bodyHealth = math.floor(GetVehicleBodyHealth(vehicle) + 0.5),
|
||||||
engineHealth = math.floor(GetVehicleEngineHealth(vehicle) + 0.5),
|
engineHealth = math.floor(GetVehicleEngineHealth(vehicle) + 0.5),
|
||||||
tankHealth = math.floor(GetVehiclePetrolTankHealth(vehicle) + 0.5),
|
tankHealth = math.floor(GetVehiclePetrolTankHealth(vehicle) + 0.5),
|
||||||
@@ -322,6 +324,10 @@ function lib.setVehicleProperties(vehicle, props, fixVehicle)
|
|||||||
SetVehicleNumberPlateTextIndex(vehicle, props.plateIndex)
|
SetVehicleNumberPlateTextIndex(vehicle, props.plateIndex)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if props.lockState ~= nil then
|
||||||
|
SetVehicleDoorsLocked(vehicle, props.lockState)
|
||||||
|
end
|
||||||
|
|
||||||
if props.bodyHealth then
|
if props.bodyHealth then
|
||||||
SetVehicleBodyHealth(vehicle, props.bodyHealth + 0.0)
|
SetVehicleBodyHealth(vehicle, props.bodyHealth + 0.0)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-hook-form": "^7.41.3",
|
"react-hook-form": "^7.41.3",
|
||||||
"react-hot-toast": "^2.4.0",
|
"react-hot-toast": "^2.5.2",
|
||||||
"react-markdown": "^8.0.1",
|
"react-markdown": "^8.0.1",
|
||||||
"remark-gfm": "^3.0.1",
|
"remark-gfm": "^3.0.1",
|
||||||
"tinycolor2": "^1.6.0",
|
"tinycolor2": "^1.6.0",
|
||||||
@@ -46,6 +46,9 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"patchedDependencies": {
|
||||||
|
"react-hot-toast@2.5.2": "patches/react-hot-toast@2.5.2.patch",
|
||||||
|
},
|
||||||
"packages": {
|
"packages": {
|
||||||
"@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
|
"@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-hook-form": "^7.41.3",
|
"react-hook-form": "^7.41.3",
|
||||||
"react-hot-toast": "^2.4.0",
|
"react-hot-toast": "^2.5.2",
|
||||||
"react-markdown": "^8.0.1",
|
"react-markdown": "^8.0.1",
|
||||||
"remark-gfm": "^3.0.1",
|
"remark-gfm": "^3.0.1",
|
||||||
"tinycolor2": "^1.6.0",
|
"tinycolor2": "^1.6.0",
|
||||||
@@ -50,9 +50,7 @@
|
|||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"rimraf": "^3.0.2"
|
"rimraf": "^3.0.2"
|
||||||
},
|
},
|
||||||
"pnpm": {
|
"patchedDependencies": {
|
||||||
"patchedDependencies": {
|
"react-hot-toast@2.5.2": "patches/react-hot-toast@2.5.2.patch"
|
||||||
"react-hot-toast@2.4.0": "patches/react-hot-toast@2.4.0.patch"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
280
web/patches/react-hot-toast@2.5.2.patch
Normal file
280
web/patches/react-hot-toast@2.5.2.patch
Normal file
File diff suppressed because one or more lines are too long
@@ -26,6 +26,7 @@ export const debugCustomNotification = () => {
|
|||||||
title: 'Error',
|
title: 'Error',
|
||||||
description: 'Notification description',
|
description: 'Notification description',
|
||||||
type: 'error',
|
type: 'error',
|
||||||
|
position: "bottom"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ const InputDialog: React.FC = () => {
|
|||||||
centered
|
centered
|
||||||
closeOnEscape={fields.options?.allowCancel !== false}
|
closeOnEscape={fields.options?.allowCancel !== false}
|
||||||
closeOnClickOutside={false}
|
closeOnClickOutside={false}
|
||||||
size="xs"
|
size={fields.options?.size || 'xs'}
|
||||||
styles={{ title: { textAlign: 'center', width: '100%', fontSize: 18 } }}
|
styles={{ title: { textAlign: 'center', width: '100%', fontSize: 18 } }}
|
||||||
title={fields.heading}
|
title={fields.heading}
|
||||||
withCloseButton={false}
|
withCloseButton={false}
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ const TextareaField: React.FC<Props> = (props) => {
|
|||||||
autosize={props.row.autosize}
|
autosize={props.row.autosize}
|
||||||
minRows={props.row.min}
|
minRows={props.row.min}
|
||||||
maxRows={props.row.max}
|
maxRows={props.row.max}
|
||||||
|
minLength={props.row.minLength}
|
||||||
|
maxLength={props.row.maxLength}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ const getAnimation = (visible: boolean, position: string) => {
|
|||||||
animation = { from: 'X(0px)', to: 'X(-100%)' };
|
animation = { from: 'X(0px)', to: 'X(-100%)' };
|
||||||
} else if (position === 'top-center') {
|
} else if (position === 'top-center') {
|
||||||
animation = { from: 'Y(0px)', to: 'Y(-100%)' };
|
animation = { from: 'Y(0px)', to: 'Y(-100%)' };
|
||||||
} else if (position === 'bottom') {
|
} else if (position === 'bottom-center') {
|
||||||
animation = { from: 'Y(0px)', to: 'Y(100%)' };
|
animation = { from: 'Y(0px)', to: 'Y(100%)' };
|
||||||
} else {
|
} else {
|
||||||
animation = { from: 'X(0px)', to: 'X(100%)' };
|
animation = { from: 'X(0px)', to: 'X(100%)' };
|
||||||
@@ -138,7 +138,7 @@ const Notifications: React.FC = () => {
|
|||||||
} else {
|
} else {
|
||||||
iconColor = tinycolor(data.iconColor).toRgbString();
|
iconColor = tinycolor(data.iconColor).toRgbString();
|
||||||
}
|
}
|
||||||
|
|
||||||
toast.custom(
|
toast.custom(
|
||||||
(t) => (
|
(t) => (
|
||||||
<Box
|
<Box
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import type { SkillCheckProps } from '../../typings';
|
import type { SkillCheckProps } from '../../typings';
|
||||||
import { useInterval } from '@mantine/hooks';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
angle: number;
|
angle: number;
|
||||||
@@ -11,15 +10,58 @@ interface Props {
|
|||||||
handleComplete: (success: boolean) => void;
|
handleComplete: (success: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Indicator: React.FC<Props> = ({ angle, offset, multiplier, handleComplete, skillCheck, className }) => {
|
const BASE_DURATION_MS = 2000;
|
||||||
|
|
||||||
|
const Indicator: React.FC<Props> = ({
|
||||||
|
angle,
|
||||||
|
offset,
|
||||||
|
multiplier,
|
||||||
|
handleComplete,
|
||||||
|
skillCheck,
|
||||||
|
className,
|
||||||
|
}) => {
|
||||||
const [indicatorAngle, setIndicatorAngle] = useState(-90);
|
const [indicatorAngle, setIndicatorAngle] = useState(-90);
|
||||||
const [keyPressed, setKeyPressed] = useState<false | string>(false);
|
const [keyPressed, setKeyPressed] = useState<false | string>(false);
|
||||||
const interval = useInterval(
|
|
||||||
() =>
|
const rafIdRef = useRef<number | null>(null);
|
||||||
setIndicatorAngle((prevState) => {
|
const startTimeRef = useRef<number | null>(null);
|
||||||
return (prevState += multiplier);
|
const completedRef = useRef(false);
|
||||||
}),
|
|
||||||
1
|
const stopAnimation = () => {
|
||||||
|
if (rafIdRef.current !== null) {
|
||||||
|
cancelAnimationFrame(rafIdRef.current);
|
||||||
|
rafIdRef.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const animate = useCallback(
|
||||||
|
(time: number) => {
|
||||||
|
if (completedRef.current) return;
|
||||||
|
|
||||||
|
if (startTimeRef.current === null) {
|
||||||
|
startTimeRef.current = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
const elapsed = time - startTimeRef.current;
|
||||||
|
|
||||||
|
const speed = Math.max(multiplier || 0, 0.0001);
|
||||||
|
const duration = BASE_DURATION_MS / speed;
|
||||||
|
|
||||||
|
const progress = Math.min(elapsed / duration, 1);
|
||||||
|
const newAngle = -90 + progress * 360;
|
||||||
|
|
||||||
|
setIndicatorAngle(newAngle);
|
||||||
|
|
||||||
|
if (newAngle + 90 >= 360) {
|
||||||
|
completedRef.current = true;
|
||||||
|
stopAnimation();
|
||||||
|
handleComplete(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rafIdRef.current = requestAnimationFrame(animate);
|
||||||
|
},
|
||||||
|
[multiplier, handleComplete]
|
||||||
);
|
);
|
||||||
const keyHandler = useCallback(
|
const keyHandler = useCallback(
|
||||||
(e: KeyboardEvent) => {
|
(e: KeyboardEvent) => {
|
||||||
@@ -42,32 +84,43 @@ const Indicator: React.FC<Props> = ({ angle, offset, multiplier, handleComplete,
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIndicatorAngle(-90);
|
setIndicatorAngle(-90);
|
||||||
|
startTimeRef.current = null;
|
||||||
|
completedRef.current = false;
|
||||||
|
|
||||||
window.addEventListener('keydown', keyHandler);
|
window.addEventListener('keydown', keyHandler);
|
||||||
interval.start();
|
rafIdRef.current = requestAnimationFrame(animate);
|
||||||
}, [skillCheck]);
|
|
||||||
|
return () => {
|
||||||
|
stopAnimation();
|
||||||
|
window.removeEventListener('keydown', keyHandler);
|
||||||
|
startTimeRef.current = null;
|
||||||
|
completedRef.current = true;
|
||||||
|
};
|
||||||
|
}, [skillCheck, keyHandler, animate]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (indicatorAngle + 90 >= 360) {
|
if (!keyPressed || completedRef.current) return;
|
||||||
interval.stop();
|
|
||||||
handleComplete(false);
|
|
||||||
}
|
|
||||||
}, [indicatorAngle]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!keyPressed) return;
|
|
||||||
|
|
||||||
if (skillCheck.keys && !skillCheck.keys?.includes(keyPressed)) return;
|
if (skillCheck.keys && !skillCheck.keys?.includes(keyPressed)) return;
|
||||||
|
|
||||||
interval.stop();
|
stopAnimation();
|
||||||
|
|
||||||
window.removeEventListener('keydown', keyHandler);
|
window.removeEventListener('keydown', keyHandler);
|
||||||
|
completedRef.current = true;
|
||||||
|
|
||||||
if (keyPressed !== skillCheck.key || indicatorAngle < angle || indicatorAngle > angle + offset)
|
if (keyPressed !== skillCheck.key || indicatorAngle < angle || indicatorAngle > angle + offset)
|
||||||
handleComplete(false);
|
handleComplete(false);
|
||||||
else handleComplete(true);
|
else handleComplete(true);
|
||||||
|
|
||||||
setKeyPressed(false);
|
setKeyPressed(false);
|
||||||
}, [keyPressed]);
|
}, [
|
||||||
|
keyPressed,
|
||||||
|
angle,
|
||||||
|
offset,
|
||||||
|
indicatorAngle,
|
||||||
|
skillCheck,
|
||||||
|
keyHandler,
|
||||||
|
handleComplete,
|
||||||
|
]);
|
||||||
|
|
||||||
return <circle transform={`rotate(${indicatorAngle}, 250, 250)`} className={className} />;
|
return <circle transform={`rotate(${indicatorAngle}, 250, 250)`} className={className} />;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export interface InputProps {
|
|||||||
rows: Array<IInput | ICheckbox | ISelect | INumber | ISlider | IColorInput | IDateInput | ITextarea | ITimeInput>;
|
rows: Array<IInput | ICheckbox | ISelect | INumber | ISlider | IColorInput | IDateInput | ITextarea | ITimeInput>;
|
||||||
options?: {
|
options?: {
|
||||||
allowCancel?: boolean;
|
allowCancel?: boolean;
|
||||||
|
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,4 +77,6 @@ export interface ITextarea extends BaseField<'textarea', string> {
|
|||||||
autosize?: boolean;
|
autosize?: boolean;
|
||||||
min?: number;
|
min?: number;
|
||||||
max?: number;
|
max?: number;
|
||||||
|
minLength?: number;
|
||||||
|
maxLength?: number;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user