diff --git a/lib/server/vehicle.lua b/lib/server/vehicle.lua index 56e5e3f..95233f2 100644 --- a/lib/server/vehicle.lua +++ b/lib/server/vehicle.lua @@ -4,7 +4,6 @@ local OxVehicle = lib.class('OxVehicle') function OxVehicle:__index(index) local value = OxVehicle[index] --[[@as any]] - if type(value) == 'function' then self[index] = value == OxVehicle.__call and function(...) return value(self, index, ...) @@ -25,7 +24,7 @@ function OxVehicle:constructor(data) end function OxVehicle:__call(...) - return exports.ox_core:CallVehicle(self.entity, ...) + return exports.ox_core:CallVehicle(self.internalId, ...) end function OxVehicle:__tostring() diff --git a/lib/server/vehicle.ts b/lib/server/vehicle.ts index bffe333..f6fa444 100644 --- a/lib/server/vehicle.ts +++ b/lib/server/vehicle.ts @@ -3,17 +3,19 @@ import type { CreateVehicleData } from 'server/vehicle'; class VehicleInterface { constructor( - public entity: number, - public netId: number, + public internalId: string, public script: string, public plate: string, public model: string, public make: string, public id?: number, + public entity?: number, + public netId?: number, public vin?: string, public owner?: number, public group?: string ) { + this.internalId = internalId; this.entity = entity; this.netId = netId; this.script = script; @@ -27,17 +29,17 @@ class VehicleInterface { } getCoords() { - return GetEntityCoords(this.entity); + return GetEntityCoords(this.entity!); } getState() { - return Entity(this.entity).state; + return Entity(this.entity!).state; } } Object.keys(exports.ox_core.GetVehicleCalls()).forEach((method: string) => { (VehicleInterface.prototype as any)[method] = function (...args: any[]) { - return exports.ox_core.CallVehicle(this.entity, method, ...args); + return exports.ox_core.CallVehicle(this.internalId, method, ...args); }; }); @@ -51,13 +53,14 @@ function CreateVehicleInstance(vehicle?: _OxVehicle) { if (!vehicle) return; return new VehicleInterface( - vehicle.entity, - vehicle.netId, + vehicle.internalId, vehicle.script, vehicle.plate, vehicle.model, vehicle.make, vehicle.id, + vehicle.entity, + vehicle.netId, vehicle.vin, vehicle.owner, vehicle.group diff --git a/server/vehicle/class.ts b/server/vehicle/class.ts index ede829a..a8d374a 100644 --- a/server/vehicle/class.ts +++ b/server/vehicle/class.ts @@ -1,5 +1,6 @@ import { ClassInterface } from 'classInterface'; -import { DeleteVehicle, IsPlateAvailable, IsVinAvailable, SaveVehicleData, SetVehicleColumn } from './db'; +import { CreateVehicle } from './index'; +import { DeleteVehicle, GetVehicleFromVin, IsPlateAvailable, IsVinAvailable, SaveVehicleData, SetVehicleColumn } from './db'; import { getRandomString, getRandomAlphanumeric, @@ -13,16 +14,19 @@ import { GetVehicleData, GetVehicleNetworkType } from '../../common/vehicles'; import { setVehicleProperties } from '@overextended/ox_lib/server'; import { Vector3 } from '@nativewrappers/fivem'; -const setEntityOrphanMode = typeof SetEntityOrphanMode !== 'undefined' ? SetEntityOrphanMode : () => {}; +type Vec3 = number[] | { x: number; y: number; z: number } | { buffer: any }; + +const setEntityOrphanMode = typeof SetEntityOrphanMode !== 'undefined' ? SetEntityOrphanMode : () => { }; export class OxVehicle extends ClassInterface { - entity: number; - netId: number; + internalId: string; script: string; plate: string; model: string; make: string; id?: number; + entity?: number; + netId?: number; vin?: string; owner?: number; group?: string; @@ -33,6 +37,7 @@ export class OxVehicle extends ClassInterface { protected static members: Dict = {}; protected static keys: Dict> = { id: {}, + entity: {}, netId: {}, vin: {}, }; @@ -54,7 +59,11 @@ export class OxVehicle extends ClassInterface { /** Get an instance of OxVehicle with the matching entityId. */ static get(entityId: string | number) { - return this.members[entityId]; + // If the entityId is a string, it's the internalId + if (typeof entityId === 'string') return this.members[entityId]; + + // If the entityId is a number, it's the entity id + return this.keys.entity[entityId]; } /** Get an instance of OxVehicle with the matching vehicleId. */ @@ -68,8 +77,16 @@ export class OxVehicle extends ClassInterface { } /** Get an instance of OxVehicle with the matching vin. */ - static getFromVin(vin: string) { - return this.keys.vin[vin]; + static async getFromVin(vin: string) { + if (this.keys.vin[vin]) { + return this.keys.vin[vin]; + } + + const vehicleDb = await GetVehicleFromVin(vin) + if (!vehicleDb) return; + + const vehicle = await CreateVehicle(vehicleDb); + return vehicle; } /** Gets all instances of OxVehicle. */ @@ -133,7 +150,6 @@ export class OxVehicle extends ClassInterface { } constructor( - entity: number, script: string, plate: string, model: string, @@ -142,13 +158,13 @@ export class OxVehicle extends ClassInterface { metadata: Dict, properties: VehicleProperties, id?: number, + entity?: number, vin?: string, owner?: number, group?: string ) { super(); - this.entity = entity; - this.netId = NetworkGetNetworkIdFromEntity(entity); + this.internalId = getRandomString('............'); this.script = script; this.plate = plate; this.model = model; @@ -161,16 +177,30 @@ export class OxVehicle extends ClassInterface { this.#metadata = metadata || {}; this.#stored = stored; - if (this.id) this.setStored(null, false); + if (entity && entity > 0) { + this.entity = entity; + this.netId = NetworkGetNetworkIdFromEntity(entity); - OxVehicle.add(this.entity, this); - SetVehicleNumberPlateText(this.entity, properties.plate || this.plate); - setVehicleProperties(entity, properties); - emit('ox:spawnedVehicle', this.entity, this.id); + if (this.id) { + this.setStored(null, false); - const state = this.getState(); + const existingVehicle = OxVehicle.getFromVehicleId(this.id); + if(existingVehicle) { + DEV: console.warn(`Vehicle with id ${this.id} already exists in the vehicle cache. Removing existing`, existingVehicle.internalId); + OxVehicle.remove(existingVehicle.internalId); + } + } + + SetVehicleNumberPlateText(this.entity, properties.plate || this.plate); + setVehicleProperties(entity, properties); + emit('ox:spawnedVehicle', this.entity, this.id); - state.set('initVehicle', true, true); + const state = this.getState(); + + state.set('initVehicle', true, true); + } + + OxVehicle.add(this.internalId, this); } /** Stores a value in the vehicle's metadata. */ @@ -184,6 +214,9 @@ export class OxVehicle extends ClassInterface { } getState() { + if (!this.entity) { + throw new Error('Vehicle does not have an entity'); + } return Entity(this.entity).state; } @@ -209,9 +242,9 @@ export class OxVehicle extends ClassInterface { despawn(save?: boolean) { const saveData = save && this.#getSaveData(); if (saveData) SaveVehicleData(saveData); - if (DoesEntityExist(this.entity)) DeleteEntity(this.entity); + if (this.entity && DoesEntityExist(this.entity)) DeleteEntity(this.entity); - OxVehicle.remove(this.entity); + this.untrack(); } delete() { @@ -219,6 +252,10 @@ export class OxVehicle extends ClassInterface { this.despawn(false); } + untrack() { + OxVehicle.remove(this.internalId); + } + setStored(value: string | null, despawn?: boolean) { this.#stored = value; @@ -254,24 +291,42 @@ export class OxVehicle extends ClassInterface { setProperties(properties: VehicleProperties, apply?: boolean) { this.#properties = properties; - if (apply) setVehicleProperties(this.entity, this.#properties); + if (apply && this.entity && DoesEntityExist(this.entity)) setVehicleProperties(this.entity, this.#properties); } - async respawn(coords?: Vector3, rotation?: Vector3) { - const hasEntity = DoesEntityExist(this.entity); - coords = Vector3.fromObject(coords || hasEntity ? GetEntityCoords(this.entity) : null); - rotation = Vector3.fromObject(rotation || hasEntity ? GetEntityRotation(this.entity) : null); + async respawn(coords?: Vec3, rotation?: Vec3) { + const hasEntity = this.entity !== undefined && DoesEntityExist(this.entity); + + if (coords) { + coords = Vector3.fromObject(coords); + } else if (hasEntity) { + coords = GetEntityCoords(this.entity!); + } else { + throw new Error('Cannot respawn vehicle without entity existing or coords provided'); + } + + if (rotation) { + rotation = Vector3.fromObject(rotation); + } else if (hasEntity) { + rotation = GetEntityRotation(this.entity!); + } else { + rotation = new Vector3(0, 0, 0); + } - OxVehicle.remove(this.entity); + coords = coords as Vector3; + rotation = rotation as Vector3; - if (hasEntity) DeleteEntity(this.entity); + if (hasEntity) DeleteEntity(this.entity!); - this.entity = OxVehicle.spawn(this.model, coords, 0); + this.untrack(); + + this.entity = OxVehicle.spawn(this.model, coords as Vector3, 0); this.netId = NetworkGetNetworkIdFromEntity(this.entity); + OxVehicle.add(this.internalId, this); + if (rotation) SetEntityRotation(this.entity, rotation.x, rotation.y, rotation.z, 2, false); - OxVehicle.add(this.entity, this); SetVehicleNumberPlateText(this.entity, this.#properties.plate || this.plate); setVehicleProperties(this.entity, this.#properties); emit('ox:spawnedVehicle', this.entity, this.id); diff --git a/server/vehicle/commands.ts b/server/vehicle/commands.ts index 3c0010b..77f4b56 100644 --- a/server/vehicle/commands.ts +++ b/server/vehicle/commands.ts @@ -33,7 +33,7 @@ addCommand<{ model: string; owner?: number }>( DeleteCurrentVehicle(ped); await sleep(200); - SetPedIntoVehicle(ped, vehicle.entity, -1); + SetPedIntoVehicle(ped, vehicle.entity!, -1); }, { help: `Spawn a vehicle with the given model.`, @@ -55,7 +55,8 @@ addCommand<{ radius?: number; owned?: string }>( async (playerId, args, raw) => { const ped = GetPlayerPed(playerId as any); - if (!args.radius) return DeleteCurrentVehicle(ped); + if (!args.radius && GetVehiclePedIsIn(ped, false) > 0) return DeleteCurrentVehicle(ped); + args.radius = args.radius ?? 2; const vehicles = await triggerClientCallback('ox:getNearbyVehicles', playerId, args.radius); diff --git a/server/vehicle/db.ts b/server/vehicle/db.ts index 08ded33..da1808c 100644 --- a/server/vehicle/db.ts +++ b/server/vehicle/db.ts @@ -9,6 +9,7 @@ export type VehicleRow = { vin: string; model: string; data: { properties: VehicleProperties; [key: string]: any }; + stored?: string; }; setImmediate(() => db.query('UPDATE vehicles SET `stored` = ? WHERE `stored` IS NULL', ['impound'])); @@ -21,9 +22,16 @@ export async function IsVinAvailable(plate: string) { return !(await db.exists('SELECT 1 FROM vehicles WHERE vin = ?', [plate])); } -export function GetStoredVehicleFromId(id: number) { +export function GetVehicleFromVin(vin: string) { return db.row( - 'SELECT id, owner, `group`, plate, vin, model, data FROM vehicles WHERE id = ? AND `stored` IS NOT NULL', + 'SELECT id, owner, `group`, plate, vin, model, data, stored FROM vehicles WHERE vin = ?', + [vin] + ); +} + +export function GetVehicleFromId(id: number) { + return db.row( + 'SELECT id, owner, `group`, plate, vin, model, data, stored FROM vehicles WHERE id = ?', [id] ); } diff --git a/server/vehicle/index.ts b/server/vehicle/index.ts index 957e9a7..249b8c6 100644 --- a/server/vehicle/index.ts +++ b/server/vehicle/index.ts @@ -1,5 +1,5 @@ import { OxVehicle } from './class'; -import { CreateNewVehicle, GetStoredVehicleFromId, IsPlateAvailable, VehicleRow } from './db'; +import { CreateNewVehicle, GetVehicleFromId, IsPlateAvailable, VehicleRow } from './db'; import { GetVehicleData } from '../../common/vehicles'; import { DEBUG } from '../../common/config'; import './class'; @@ -39,7 +39,7 @@ export async function CreateVehicle( const vehicle = OxVehicle.getFromVehicleId(data.id); if (vehicle) { - if (DoesEntityExist(vehicle.entity)) { + if (vehicle.entity && DoesEntityExist(vehicle.entity)) { return vehicle; } @@ -78,10 +78,7 @@ export async function CreateVehicle( ); } - if (!entity) return; - return new OxVehicle( - entity, invokingScript, data.plate, data.model, @@ -90,6 +87,7 @@ export async function CreateVehicle( metadata, properties, data.id, + entity, data.vin, data.owner, data.group @@ -98,9 +96,9 @@ export async function CreateVehicle( export async function SpawnVehicle(id: number, coords: Vec3, heading?: number) { const invokingScript = GetInvokingResource(); - const vehicle = await GetStoredVehicleFromId(id); + const vehicle = await GetVehicleFromId(id); - if (!vehicle) return; + if (!vehicle || !vehicle.stored) return; return await CreateVehicle(vehicle, coords, heading, invokingScript); }