lenix 1.6.3 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,11 @@
1
+ // deno-lint-ignore-file no-undef
2
+ import type { _InternalRequests } from '../shared/types.ts'
3
+ import { onNui } from './nui.ts'
4
+
5
+ onNui<_InternalRequests['focus']>('__nuiFocus', ({
6
+ keyboard,
7
+ cursor
8
+ }) => {
9
+ SetNuiFocus(keyboard, cursor)
10
+ return true
11
+ })
package/client/blip.ts ADDED
@@ -0,0 +1,51 @@
1
+ // deno-lint-ignore-file no-undef
2
+
3
+ import type { Vec4 } from '../shared/types.ts'
4
+
5
+
6
+ export interface Blip {
7
+ /**
8
+ * Blip world coordinates and heading.
9
+ */
10
+ coords: Vec4
11
+ /**
12
+ * FiveM blip sprite id.
13
+ */
14
+ icon: number
15
+ }
16
+
17
+ const blips = new Set<number>()
18
+
19
+ /**
20
+ * Creates a tracked map blip.
21
+ */
22
+ const create = ({
23
+ coords,
24
+ icon
25
+ }: Blip): number => {
26
+ const blip = AddBlipForCoord(coords[0], coords[1], coords[2])
27
+
28
+ SetBlipSprite(blip, icon)
29
+ blips.add(blip)
30
+
31
+ return blip
32
+ }
33
+
34
+ /**
35
+ * Removes a tracked map blip.
36
+ */
37
+ const destroy = (blip: number): void => {
38
+ RemoveBlip(blip)
39
+ blips.delete(blip)
40
+ }
41
+
42
+ export const blip = {
43
+ create,
44
+ destroy
45
+ }
46
+
47
+ on('onResourceStop', (resourceName: string) => {
48
+ if (resourceName !== GetCurrentResourceName()) return
49
+
50
+ blips.forEach(destroy)
51
+ })
package/client/cam.ts ADDED
@@ -0,0 +1,141 @@
1
+ // deno-lint-ignore-file no-undef
2
+
3
+ import type { Vec4 } from '../shared/types.ts'
4
+
5
+ export interface BaseCamDetails {
6
+ /**
7
+ * Fade-out duration in milliseconds.
8
+ */
9
+ fadeOut?: number
10
+ /**
11
+ * Fade-in duration in milliseconds.
12
+ */
13
+ fadeIn?: number
14
+ /**
15
+ * Delay before the camera state changes.
16
+ */
17
+ delay?: number
18
+ }
19
+
20
+ export interface CreateCamSettings {
21
+ /**
22
+ * Camera coordinates and heading.
23
+ */
24
+ coords: Vec4
25
+ /**
26
+ * Camera vertical and horizontal rotation.
27
+ */
28
+ rotation: {
29
+ vertical: number,
30
+ horizontal: number
31
+ }
32
+ /**
33
+ * Optional camera field-of-view, rotation order, and fade settings.
34
+ */
35
+ details?: {
36
+ fov?: number,
37
+ rotationOrder?: number
38
+ } & BaseCamDetails
39
+ }
40
+
41
+ export interface DestroyCamSettings {
42
+ /**
43
+ * Camera handle to deactivate.
44
+ */
45
+ cam: number
46
+ /**
47
+ * Optional fade settings.
48
+ */
49
+ details?: BaseCamDetails
50
+ }
51
+
52
+ const toggleCam = ({
53
+ cam,
54
+ state,
55
+ delay,
56
+ fadeIn,
57
+ fadeOut
58
+ }: {
59
+ cam: number,
60
+ state: boolean,
61
+ delay: number,
62
+ fadeIn: number,
63
+ fadeOut: number
64
+ }) => {
65
+ setTimeout(() => {
66
+ SetCamActive(cam, state)
67
+ // deno-lint-ignore no-boolean-literal-for-arguments
68
+ RenderScriptCams(state, state, delay, true, state)
69
+ DoScreenFadeIn(fadeIn)
70
+ }, delay)
71
+ DoScreenFadeOut(fadeOut)
72
+ }
73
+
74
+ /**
75
+ * Creates and activates a scripted camera.
76
+ */
77
+ const create = ({
78
+ coords,
79
+ rotation: {
80
+ vertical,
81
+ horizontal
82
+ },
83
+ details
84
+ }: CreateCamSettings): number => {
85
+ const fov = details?.fov ?? 40.0,
86
+ fadeOut = details?.fadeOut ?? 0,
87
+ fadeIn = details?.fadeIn ?? 0,
88
+ delay = details?.delay ?? 0,
89
+ rotationOrder = details?.rotationOrder ?? 0
90
+
91
+ DoScreenFadeOut(fadeOut)
92
+ // deno-lint-ignore no-boolean-literal-for-arguments
93
+ const cam = CreateCamWithParams(
94
+ 'DEFAULT_SCRIPTED_CAMERA',
95
+ coords[0],
96
+ coords[1],
97
+ coords[2],
98
+ vertical,
99
+ horizontal,
100
+ coords[3],
101
+ fov,
102
+ false,
103
+ rotationOrder
104
+ )
105
+
106
+ toggleCam({
107
+ cam,
108
+ state: true,
109
+ delay,
110
+ fadeIn,
111
+ fadeOut
112
+ })
113
+
114
+ return cam
115
+ }
116
+
117
+ /**
118
+ * Deactivates a scripted camera.
119
+ */
120
+ const destroy = ({
121
+ cam,
122
+ details
123
+ }: DestroyCamSettings): void => {
124
+ const fadeOut = details?.fadeOut ?? 0,
125
+ fadeIn = details?.fadeIn ?? 0,
126
+ delay = details?.delay ?? 0
127
+
128
+ toggleCam({
129
+ cam,
130
+ state: false,
131
+ delay,
132
+ fadeIn,
133
+ fadeOut
134
+ })
135
+ }
136
+
137
+ // TODO: delete cams on resource stop
138
+ export const cam = {
139
+ create,
140
+ destroy
141
+ }
@@ -0,0 +1,22 @@
1
+ // deno-lint-ignore-file no-undef
2
+ import type { Vec3, Vec4 } from '../shared/types.ts'
3
+
4
+ /**
5
+ * Gets entity coordinates and heading.
6
+ */
7
+ function coords(excludeH: true, entity?: number, isAlive?: boolean): Vec3
8
+ function coords(excludeH?: false, entity?: number, isAlive?: boolean): Vec4
9
+ function coords(excludeH = false, entity = PlayerPedId(), isAlive = true): Vec3 | Vec4 {
10
+ const entityCoords = GetEntityCoords(entity, isAlive) as Vec3
11
+
12
+ if (excludeH) return entityCoords
13
+
14
+ return [
15
+ ...entityCoords,
16
+ GetEntityHeading(entity)
17
+ ]
18
+ }
19
+
20
+ export const getEntity = {
21
+ coords
22
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * @module
3
+ *
4
+ * FiveM client-side helpers for NUI messages, blips, cameras, entities, pools,
5
+ * and nearest-entity lookups.
6
+ */
7
+
8
+ import './_init.ts'
9
+ export * from './nui.ts'
10
+ export * from './blip.ts'
11
+ export * from './cam.ts'
12
+ export * from './nearest.ts'
13
+ export * from './pool.ts'
14
+ export * from './entity.ts'
15
+ export * from './timer.ts'
@@ -0,0 +1,115 @@
1
+ // deno-lint-ignore-file no-undef
2
+ import type { Vec3 } from '../shared/types.ts'
3
+ import { getEntity } from './entity.ts'
4
+
5
+ /**
6
+ * Finds the nearest coordinates from a list.
7
+ */
8
+ const coords = (coords: Vec3, zones: Vec3[]): Vec3 | undefined => {
9
+ let closest: Vec3 | undefined
10
+ let closestDistance = Infinity
11
+
12
+ for (const zone of zones) {
13
+ const dx = zone[0] - coords[0]
14
+ const dy = zone[1] - coords[1]
15
+ const dz = zone[2] - coords[2]
16
+ const distance = dx * dx + dy * dy + dz * dz
17
+
18
+ if (distance < closestDistance) {
19
+ closestDistance = distance
20
+ closest = zone
21
+ }
22
+ }
23
+
24
+ return closest
25
+ }
26
+
27
+ /**
28
+ * Finds the nearest player around the provided coordinates.
29
+ */
30
+ const player = (
31
+ coords: Vec3,
32
+ maxDistance = 2.0,
33
+ includePlayer = false
34
+ ): {
35
+ playerId?: number,
36
+ playerPed?: number,
37
+ playerCoords?: Vec3,
38
+ playerVehicle?: number
39
+ } => {
40
+ const players = GetActivePlayers()
41
+
42
+ let closestId: number | undefined
43
+ let closestPed: number | undefined
44
+ let closestCoords: Vec3 | undefined
45
+ let closestVehicle: number | undefined
46
+
47
+ for (let i = 0; i < players.length; i++) {
48
+ const playerId = players[i]
49
+
50
+ if (playerId !== PlayerPedId() || includePlayer) {
51
+ const playerPed = GetPlayerPed(playerId)
52
+ // deno-lint-ignore no-boolean-literal-for-arguments
53
+ const vehicle = GetVehiclePedIsIn(playerPed, false)
54
+ const playerCoords: Vec3 = (
55
+ vehicle === 0 ? getEntity.coords(true, playerPed) : GetWorldPositionOfEntityBone(playerPed, 0) as Vec3
56
+ )
57
+
58
+ const distance = Vdist(
59
+ coords[0],
60
+ coords[1],
61
+ coords[2],
62
+ playerCoords[0],
63
+ playerCoords[1],
64
+ playerCoords[2]
65
+ )
66
+
67
+ if (distance < maxDistance) {
68
+ maxDistance = distance
69
+ closestId = playerId
70
+ closestPed = playerPed
71
+ closestCoords = playerCoords
72
+ closestVehicle = vehicle
73
+ }
74
+ }
75
+ }
76
+
77
+ return {
78
+ playerId: closestId,
79
+ playerPed: closestPed,
80
+ playerCoords: closestCoords,
81
+ playerVehicle: closestVehicle
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Finds the nearest vehicle around an entity.
87
+ */
88
+ const vehicle = (entity: number, radialSpace: number): number | undefined => {
89
+ const coords: Vec3 = getEntity.coords(true, entity)
90
+ const vehicles = GetGamePool('CVehicle') as number[]
91
+
92
+ let closest: number | undefined
93
+ let closestDistance = radialSpace
94
+
95
+ for (const vehicle of vehicles) {
96
+ const vehCoords: Vec3 = getEntity.coords(true, vehicle)
97
+ const x = coords[0] - vehCoords[0]
98
+ const y = coords[1] - vehCoords[1]
99
+ const z = coords[2] - vehCoords[2]
100
+ const distance = Math.sqrt(x * x + y * y + z * z)
101
+
102
+ if (distance < closestDistance) {
103
+ closestDistance = distance
104
+ closest = vehicle
105
+ }
106
+ }
107
+
108
+ return closest
109
+ }
110
+
111
+ export const getNearest = {
112
+ player,
113
+ vehicle,
114
+ coords
115
+ }
package/client/nui.ts ADDED
@@ -0,0 +1,28 @@
1
+ // deno-lint-ignore-file no-undef
2
+ import type { Request } from '../shared/types.ts'
3
+
4
+ /**
5
+ * Sends an event from the game client to the NUI browser.
6
+ */
7
+ export const emitEvent = <T extends [string, unknown[]]>(id: T[0], ...params: T[1]): void => {
8
+ if (
9
+ !SendNuiMessage(
10
+ JSON.stringify({
11
+ id,
12
+ params: [...params]
13
+ })
14
+ )
15
+ ) {
16
+ throw new Error('SendNuiMessage returned falsy')
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Registers a typed NUI callback on the game client.
22
+ */
23
+ export const onNui = <T extends Request<unknown, string, object>>(
24
+ id: T[1],
25
+ cb: (data: T[2]) => T[0]
26
+ ): void => {
27
+ RegisterNuiCallback(id, (data: T[2], reply: (_: unknown) => void) => reply(cb(data)))
28
+ }
package/client/pool.ts ADDED
@@ -0,0 +1,30 @@
1
+ // deno-lint-ignore-file no-undef
2
+ const tasks = new Set<() => void>()
3
+ let running = false
4
+
5
+ /**
6
+ * Adds a task to a shared FiveM tick pool.
7
+ */
8
+ export const pool = (func: () => void): void => {
9
+ tasks.add(func)
10
+
11
+ if (running) return
12
+ running = true
13
+
14
+ const tick = setTick(() => {
15
+ if (!tasks.size) {
16
+ clearTick(tick)
17
+ running = false
18
+ return
19
+ }
20
+ for (const task of tasks) {
21
+ try {
22
+ task()
23
+ } catch (e) {
24
+ tasks.delete(task)
25
+ // deno-lint-ignore no-console
26
+ console.warn(e)
27
+ }
28
+ }
29
+ })
30
+ }
@@ -0,0 +1,28 @@
1
+ // deno-lint-ignore-file no-undef
2
+
3
+ /**
4
+ * Starts a client timer using FiveM game time.
5
+ */
6
+ export const useTimer = (
7
+ duration: number,
8
+ updateInterval: number,
9
+ onTick: (timeLeft: number) => void,
10
+ onEnd: () => void
11
+ ): () => void => {
12
+ const start = GetGameTimer()
13
+
14
+ const interval = setInterval(() => {
15
+ const elapsed = GetGameTimer() - start
16
+ const timeLeft = duration - elapsed
17
+
18
+ if (elapsed >= duration) {
19
+ clearInterval(interval)
20
+ onEnd()
21
+ return
22
+ }
23
+
24
+ onTick(timeLeft)
25
+ }, updateInterval)
26
+
27
+ return () => clearInterval(interval)
28
+ }
package/nui/focus.ts ADDED
@@ -0,0 +1,10 @@
1
+ import type { _InternalRequests } from '../shared/types.ts'
2
+ import { triggerNui } from './trigger.ts'
3
+
4
+ /**
5
+ * Requests keyboard and cursor focus for the NUI browser.
6
+ */
7
+ export const focus = (
8
+ keyboard = true,
9
+ cursor = true
10
+ ): Promise<true> => triggerNui<_InternalRequests['focus']>('__nuiFocus', { keyboard, cursor })
package/nui/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @module
3
+ *
4
+ * Browser-side NUI helpers for listening to game events, sending NUI requests,
5
+ * and controlling NUI focus.
6
+ */
7
+
8
+ export * from './on.ts'
9
+ export * from './trigger.ts'
10
+ export * from './focus.ts'
package/nui/on.ts ADDED
@@ -0,0 +1,29 @@
1
+ import type { Event } from '../shared/types.ts'
2
+
3
+ const cbs = new Map<string, (...params: unknown[]) => void>()
4
+
5
+ // TODO: remove the event
6
+ // deno-lint-ignore no-window no-window-prefix no-unused-vars
7
+ const handler = window.addEventListener('message', (event: MessageEvent) => {
8
+ const { id, params } = event.data
9
+ const cb = cbs.get(id)
10
+ if (!cb) throw new Error(`Callback<${id}> does not exist yet`)
11
+
12
+ try {
13
+ cb(...(params ?? []))
14
+ } catch (e) {
15
+ throw new Error(`Error occured while receiving event<${id}>. \n${e}`)
16
+ }
17
+ })
18
+
19
+ /**
20
+ * Registers a typed browser-side handler for events sent from the game client.
21
+ */
22
+ export const onEvent = <
23
+ T extends Event<string, unknown[]>
24
+ >(
25
+ id: T[0],
26
+ cb: (...params: T[1]) => void
27
+ ): void => {
28
+ cbs.set(id, cb)
29
+ }
package/nui/trigger.ts ADDED
@@ -0,0 +1,24 @@
1
+ import type { Request } from '../shared/types.ts'
2
+
3
+ declare function GetParentResourceName(): string
4
+
5
+ /**
6
+ * Sends a typed request from the NUI browser to the game client.
7
+ */
8
+ export const triggerNui = async <T extends Request<unknown, string, object>>(
9
+ id: T[1],
10
+ data?: T[2]
11
+ ): Promise<T[0]> => {
12
+ try {
13
+ const response = await fetch(`https://${GetParentResourceName()}/${id}`, {
14
+ method: 'post',
15
+ headers: {
16
+ 'Content-Type': 'application/json; charset=UTF-8'
17
+ },
18
+ body: JSON.stringify(data ?? {})
19
+ })
20
+ return await response.json()
21
+ } catch (e) {
22
+ throw new Error(`Error occured while emiting an nui<${id}>.\n${e}`)
23
+ }
24
+ }
package/package.json CHANGED
@@ -1,11 +1,10 @@
1
1
  {
2
2
  "name": "lenix",
3
- "version": "1.6.3",
4
- "description": "The All-in-one Package",
3
+ "version": "1.7.0",
4
+ "description": "Typed FiveM utilities for client, server, shared, and NUI scripts",
5
5
  "author": "Lenix",
6
- "license": "GPL-3.0",
6
+ "license": "MIT",
7
7
  "type": "module",
8
- "readme": "modules/README.md",
9
8
  "keywords": [
10
9
  "lenix",
11
10
  "modules",
@@ -22,48 +21,28 @@
22
21
  ],
23
22
  "repository": {
24
23
  "type": "git",
25
- "url": "git+https://github.com/lenixdev/lenix.git"
24
+ "url": "git+https://github.com/jonahmr1/lenix.git"
26
25
  },
27
- "main": "./modules/src/index.ts",
28
- "types": "./modules/src/index.ts",
29
26
  "exports": {
30
- ".": "./modules/src/index.ts",
31
- "./lint": "./modules/src/lint/preset.json"
27
+ ".": "./shared/index.ts",
28
+ "./client": "./client/index.ts",
29
+ "./server": "./server/index.ts",
30
+ "./nui": "./nui/index.ts"
32
31
  },
33
32
  "files": [
34
- "modules/src",
35
- "format",
36
- "oop",
37
- "lint/dist"
33
+ "client",
34
+ "server",
35
+ "shared",
36
+ "nui"
38
37
  ],
39
- "bin": {
40
- "mac-lua-modules": "oop/mac-lua-modules.sh",
41
- "lenix": "./index.ts"
42
- },
43
- "dependencies": {
44
- "@clack/prompts": "^1.1.0",
45
- "eslint-plugin-import": "^2.32.0",
46
- "eslint-plugin-react": "^7.37.5",
47
- "eslint-plugin-react-hooks": "^7.0.1"
38
+ "scripts": {
39
+ "prepare": "rm -rf ./client ./server ./shared ./nui && cp -R ../modules/client ./client && cp -R ../modules/server ./server && cp -R ../modules/shared ./shared && cp -R ../modules/nui ./nui && cp -R ../LICENSE ./",
40
+ "packpreview": "rm -rf ./package-preview && mkdir -p ./package-preview && bun pm pack --destination ./package-preview && tar -xzf ./package-preview/*.tgz -C ./package-preview && find ./package-preview/package -type f | sort"
48
41
  },
49
42
  "devDependencies": {
50
- "@eslint/js": "^10.0.1",
51
- "@types/eslint": "^9.6.1",
52
- "@types/node": "^20.7.1",
53
- "eslint": "^10.0.3",
54
- "eslint-config-prettier": "10.1.8",
55
- "globals": "^17.4.0",
56
- "jiti": "^2.6.1",
57
- "prettier": "3.8.1",
58
- "tsc-alias": "^1.8.16",
59
- "tsx": "^4.21.0",
60
- "typescript": "^5.3.3",
61
- "typescript-eslint": "^8.57.0"
43
+ "@types/bun": "latest"
62
44
  },
63
- "scripts": {
64
- "build-lint": "tsc -p lint/src/tsconfig.json",
65
- "bump": "pnpm version patch",
66
- "format": "pnpm exec prettier . --write",
67
- "lint": "pnpm exec eslint ."
45
+ "peerDependencies": {
46
+ "typescript": "^5"
68
47
  }
69
- }
48
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @module
3
+ *
4
+ * FiveM server-side helpers.
5
+ */
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Asserts that a condition is true and narrows its type.
3
+ */
4
+ export const asserts = <T extends boolean>(
5
+ condition: T,
6
+ errorMessage = 'assertion did not passed successfuly'
7
+ ): asserts condition => {
8
+ if (condition) return
9
+
10
+ throw new Error(errorMessage)
11
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @module
3
+ *
4
+ * Shared helpers that can be used from both client and server contexts.
5
+ */
6
+
7
+ export * from './assert.ts'
8
+ export * from './types.ts'
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Three-dimensional coordinates plus heading.
3
+ */
4
+ export type Vec4 = [number, number, number, number]
5
+
6
+ /**
7
+ * Three-dimensional coordinates.
8
+ */
9
+ export type Vec3 = [number, number, number]
10
+
11
+ /**
12
+ * Typed event tuple containing an event id and positional parameters.
13
+ */
14
+ export type Event<Id extends string, Params extends unknown[] = never> = [Id, Params]
15
+
16
+ /**
17
+ * Typed request tuple containing a response type, request id, and object payload.
18
+ */
19
+ export type Request<
20
+ Response,
21
+ Id extends string,
22
+ Params extends object = { [key: string]: unknown }
23
+ > = Params extends readonly unknown[] ? never
24
+ : [Response, Id, Params]
25
+
26
+
27
+ /**
28
+ * Internal NUI requests used by Lenix helpers. DO NOT USE
29
+ */
30
+ export interface _InternalRequests {
31
+ focus: Request<true, '__nuiFocus', {
32
+ keyboard: boolean,
33
+ cursor: boolean
34
+ }>
35
+ }
36
+