lenix 1.6.4 → 1.7.1

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,50 @@
1
+ // deno-lint-ignore-file no-undef
2
+
3
+ import type { Vec4 } from '../shared/types.ts'
4
+
5
+ export interface Blip {
6
+ /**
7
+ * Blip world coordinates and heading.
8
+ */
9
+ coords: Vec4
10
+ /**
11
+ * FiveM blip sprite id.
12
+ */
13
+ icon: number
14
+ }
15
+
16
+ const blips = new Set<number>()
17
+
18
+ /**
19
+ * Creates a tracked map blip.
20
+ */
21
+ const create = ({
22
+ coords,
23
+ icon
24
+ }: Blip): number => {
25
+ const blip = AddBlipForCoord(coords[0], coords[1], coords[2])
26
+
27
+ SetBlipSprite(blip, icon)
28
+ blips.add(blip)
29
+
30
+ return blip
31
+ }
32
+
33
+ /**
34
+ * Removes a tracked map blip.
35
+ */
36
+ const destroy = (blip: number): void => {
37
+ RemoveBlip(blip)
38
+ blips.delete(blip)
39
+ }
40
+
41
+ export const blip = {
42
+ create,
43
+ destroy
44
+ }
45
+
46
+ on('onResourceStop', (resourceName: string) => {
47
+ if (resourceName !== GetCurrentResourceName()) return
48
+
49
+ blips.forEach(destroy)
50
+ })
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,98 @@
1
+ import type { Vec3, Vec4 } from '../shared/types.ts'
2
+
3
+ export const client: {
4
+ entity: {
5
+ handle: (entityNetId?: number) => number;
6
+ netId: (entity?: number) => number;
7
+ coords: {
8
+ (excludeH: true, entity?: number, isAlive?: boolean): Vec3;
9
+ (excludeH?: false, entity?: number, isAlive?: boolean): Vec4;
10
+ };
11
+ };
12
+ player: {
13
+ serverId: (playerId?: number) => number;
14
+ id: (serverId?: number) => number;
15
+ };
16
+ } = {
17
+ entity: {
18
+ /**
19
+ * Gets the local entity handle from a network ID.
20
+ *
21
+ * Network IDs are shared, but entity handles are local.
22
+ * The returned handle is only valid on the current machine.
23
+ *
24
+ * If no network ID is provided, this resolves your own player ped
25
+ * by first getting its network ID.
26
+ *
27
+ * Examples:
28
+ * - entity(vehicleNetId) -> local vehicle handle
29
+ * - entity(pedNetId) -> local ped handle
30
+ * - entity() -> your own player ped handle
31
+ */
32
+ handle: (entityNetId = client.entity.netId()) => NetworkGetEntityFromNetworkId(entityNetId),
33
+
34
+ /**
35
+ * Gets the network ID for an entity.
36
+ *
37
+ * Entity handles are local to the current machine.
38
+ * Network IDs are shared references for networked entities.
39
+ *
40
+ * If no entity is provided, this uses your own player ped.
41
+ *
42
+ * Examples:
43
+ * - netId(vehicle) -> network ID of that vehicle
44
+ * - netId(targetPed) -> network ID of that ped
45
+ * - netId() -> network ID of your own player ped
46
+ */
47
+ netId: (entity = PlayerPedId()) => NetworkGetNetworkIdFromEntity(entity),
48
+
49
+ /**
50
+ * Gets entity coordinates and heading.
51
+ */
52
+ coords: (() => {
53
+ function coords(excludeH: true, entity?: number, isAlive?: boolean): Vec3
54
+ function coords(excludeH?: false, entity?: number, isAlive?: boolean): Vec4
55
+ function coords(excludeH = false, entity = client.entity.handle(), isAlive = true) {
56
+ const entityCoords = GetEntityCoords(entity, isAlive) as Vec3
57
+
58
+ if (excludeH) return entityCoords
59
+
60
+ return [
61
+ ...entityCoords,
62
+ GetEntityHeading(entity)
63
+ ]
64
+ }
65
+
66
+ return coords
67
+ })()
68
+ },
69
+ player: {
70
+ /**
71
+ * Gets a client player ID.
72
+ *
73
+ * If a server ID is provided, this converts that server ID into a client
74
+ * player ID on the current client.
75
+ *
76
+ * If no server ID is provided, this returns your own client player ID.
77
+ *
78
+ * Examples:
79
+ * - id(targetServerId) -> target player's client player ID
80
+ * - id() -> your own client player ID
81
+ */
82
+ id: (serverId?: number) => serverId ? GetPlayerFromServerId(serverId) : PlayerId(),
83
+
84
+ /**
85
+ * Gets a player's server ID from their client player ID.
86
+ *
87
+ * Client player IDs are local to each client.
88
+ * Server IDs, also called source IDs, identify connected players on the server.
89
+ *
90
+ * If no client player ID is provided, this uses your own client player ID.
91
+ *
92
+ * Examples:
93
+ * - serverId(targetPlayerId) -> target player's server ID
94
+ * - serverId() -> your own server ID
95
+ */
96
+ serverId: (playerId = client.player.id()) => GetPlayerServerId(playerId),
97
+ }
98
+ }
@@ -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 './timer.ts'
15
+ export * from './client.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 = vehicle === 0
55
+ ? getEntity.coords(true, playerPed)
56
+ : GetWorldPositionOfEntityBone(playerPed, 0) as Vec3
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.4",
4
- "description": "The All-in-one Package",
3
+ "version": "1.7.1",
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,45 +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"
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"
45
41
  },
46
42
  "devDependencies": {
47
- "@eslint/js": "^10.0.1",
48
- "@types/eslint": "^9.6.1",
49
- "@types/node": "^20.7.1",
50
- "eslint": "^10.0.3",
51
- "eslint-config-prettier": "10.1.8",
52
- "globals": "^17.4.0",
53
- "jiti": "^2.6.1",
54
- "prettier": "3.8.1",
55
- "tsc-alias": "^1.8.16",
56
- "tsx": "^4.21.0",
57
- "typescript": "^5.3.3",
58
- "typescript-eslint": "^8.57.0"
43
+ "@types/bun": "latest"
59
44
  },
60
- "scripts": {
61
- "build-lint": "tsc -p lint/src/tsconfig.json",
62
- "bump": "pnpm version patch",
63
- "format": "pnpm exec prettier . --write",
64
- "lint": "pnpm exec eslint ."
45
+ "peerDependencies": {
46
+ "typescript": "^5"
65
47
  }
66
- }
48
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @module
3
+ *
4
+ * FiveM server-side helpers.
5
+ */
6
+
7
+ export * from './server'
@@ -0,0 +1,11 @@
1
+ export const server: {
2
+ entity: {
3
+ handle: (netId: number) => number;
4
+ handleFromSource: (source: number) => number;
5
+ };
6
+ } = {
7
+ entity: {
8
+ handle: (netId: number) => NetworkGetEntityFromNetworkId(netId),
9
+ handleFromSource: (source: number) => GetPlayerPed(source.toString())
10
+ }
11
+ }