Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature(server/vehicle) Add support for getting OxVehicle even when vehicle not tracked #213

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions lib/server/vehicle.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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, ...)
Expand All @@ -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()
Expand Down
17 changes: 10 additions & 7 deletions lib/server/vehicle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
};
});

Expand All @@ -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
Expand Down
111 changes: 83 additions & 28 deletions server/vehicle/class.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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;
Expand All @@ -33,6 +37,7 @@ export class OxVehicle extends ClassInterface {
protected static members: Dict<OxVehicle> = {};
protected static keys: Dict<Dict<OxVehicle>> = {
id: {},
entity: {},
netId: {},
vin: {},
};
Expand All @@ -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. */
Expand All @@ -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. */
Expand Down Expand Up @@ -133,7 +150,6 @@ export class OxVehicle extends ClassInterface {
}

constructor(
entity: number,
script: string,
plate: string,
model: string,
Expand All @@ -142,13 +158,13 @@ export class OxVehicle extends ClassInterface {
metadata: Dict<any>,
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;
Expand All @@ -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. */
Expand All @@ -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;
}

Expand All @@ -209,16 +242,20 @@ 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() {
if (this.id) DeleteVehicle(this.id);
this.despawn(false);
}

untrack() {
OxVehicle.remove(this.internalId);
}

setStored(value: string | null, despawn?: boolean) {
this.#stored = value;

Expand Down Expand Up @@ -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);
Expand Down
5 changes: 3 additions & 2 deletions server/vehicle/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.`,
Expand All @@ -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<number[]>('ox:getNearbyVehicles', playerId, args.radius);

Expand Down
12 changes: 10 additions & 2 deletions server/vehicle/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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']));
Expand All @@ -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<VehicleRow>(
'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<VehicleRow>(
'SELECT id, owner, `group`, plate, vin, model, data, stored FROM vehicles WHERE id = ?',
[id]
);
}
Expand Down
12 changes: 5 additions & 7 deletions server/vehicle/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -78,10 +78,7 @@ export async function CreateVehicle(
);
}

if (!entity) return;

return new OxVehicle(
entity,
invokingScript,
data.plate,
data.model,
Expand All @@ -90,6 +87,7 @@ export async function CreateVehicle(
metadata,
properties,
data.id,
entity,
data.vin,
data.owner,
data.group
Expand All @@ -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);
}
Expand Down