Skip to main content

samp_sdk/omp/
vehicles.rs

1//! The Open Multiplayer vehicle component.
2//!
3//! `IVehiclesComponent` is queried like any other component, by UID. From it a
4//! plugin creates vehicles and reaches the vehicle event dispatcher; each
5//! `IVehicle` answers for its own model and health, and — through the `IEntity`
6//! subobject it carries, exactly like a player — for its position.
7//!
8//! ## Slots, per ABI
9//!
10//! | Method | Itanium | MSVC |
11//! | ------ | :-----: | :--: |
12//! | `IVehiclesComponent::create(bool, int, Vector3, …)` | 19 | **18** |
13//! | `IVehiclesComponent::getEventDispatcher` | 21 | 19 |
14//! | `IVehicle::setColour` | 11 | 10 |
15//! | `IVehicle::setHealth` | 13 | 12 |
16//! | `IVehicle::getHealth` | 14 | 13 |
17//! | `IVehicle::getModel` | 57 | 56 |
18//!
19//! `create` is overloaded — the other one takes a `VehicleSpawnData` — so MSVC
20//! emits the pair in reverse: the eight-argument overload the SDK calls lands
21//! at [18] there, with [17] holding the other one. That is the same trap the
22//! timer component sprang in v3.5.0, and `cargo xtask vtable` reports it
23//! without anyone having to remember the rule.
24
25use super::component_api::ComponentInterface;
26use super::players::{ENTITY_OFFSET, SLOT_ENTITY_GET_POSITION};
27use super::server::ServerComponent;
28use super::types::{UID, Vector3};
29use super::vtable::{call_vtable, opaque, slots, virtual_fns};
30
31/// UID of the Open Multiplayer `Vehicles` component.
32pub const VEHICLES_COMPONENT_UID: UID = 0x3f1f_62ee_9e22_ab19;
33
34slots! {
35    SLOT_CREATE_VEHICLE: usize = 19, 18;
36    SLOT_VEHICLE_SET_COLOUR: usize = 11, 10;
37    SLOT_VEHICLE_SET_HEALTH: usize = 13, 12;
38    SLOT_VEHICLE_GET_HEALTH: usize = 14, 13;
39    SLOT_VEHICLE_GET_MODEL: usize = 57, 56;
40    /// Slot of `IVehiclesComponent::getEventDispatcher()`.
41    SLOT_VEHICLE_DISPATCHER: usize = 21, 19;
42    /// Offset of the `IReadOnlyPool<IVehicle>` subobject inside the component.
43    ///
44    /// Larger than the player pool's because `IVehiclesComponent` reaches it
45    /// through `IComponent`, which carries an `IUIDProvider` of its own. From
46    /// clang's record layout.
47    VEHICLE_POOL_OFFSET: isize = 44, 64;
48}
49
50opaque! {
51    /// Opaque handle for the server's `IVehiclesComponent*`.
52    pub IVehiclesComponent;
53    /// Opaque handle for the server's `IVehicle*`.
54    pub IVehicle;
55    /// Opaque handle for `IEventDispatcher<VehicleEventHandler>*`.
56    pub IVehicleDispatcher;
57}
58
59/// `VehicleEventHandler` vtable — Itanium ABI.
60///
61/// Fourteen slots, in the order `vehicles.hpp` declares them. No virtual
62/// destructor, so MSVC numbers them the same; only the calling convention
63/// differs. The handlers returning `bool` can refuse the action: a `false` from
64/// `on_vehicle_paint_job`, `on_vehicle_mod` or `on_vehicle_respray` rejects it.
65#[cfg(not(target_env = "msvc"))]
66#[repr(C)]
67pub struct VehicleHandlerVTable {
68    pub on_vehicle_stream_in: unsafe extern "C" fn(*mut VehicleHandler, *mut IVehicle, *mut u8),
69    pub on_vehicle_stream_out: unsafe extern "C" fn(*mut VehicleHandler, *mut IVehicle, *mut u8),
70    pub on_vehicle_death: unsafe extern "C" fn(*mut VehicleHandler, *mut IVehicle, *mut u8),
71    pub on_player_enter_vehicle:
72        unsafe extern "C" fn(*mut VehicleHandler, *mut u8, *mut IVehicle, bool),
73    pub on_player_exit_vehicle: unsafe extern "C" fn(*mut VehicleHandler, *mut u8, *mut IVehicle),
74    pub on_vehicle_damage_status_update:
75        unsafe extern "C" fn(*mut VehicleHandler, *mut IVehicle, *mut u8),
76    pub on_vehicle_paint_job:
77        unsafe extern "C" fn(*mut VehicleHandler, *mut u8, *mut IVehicle, i32) -> bool,
78    pub on_vehicle_mod:
79        unsafe extern "C" fn(*mut VehicleHandler, *mut u8, *mut IVehicle, i32) -> bool,
80    pub on_vehicle_respray:
81        unsafe extern "C" fn(*mut VehicleHandler, *mut u8, *mut IVehicle, i32, i32) -> bool,
82    pub on_enter_exit_mod_shop: unsafe extern "C" fn(*mut VehicleHandler, *mut u8, bool, i32),
83    pub on_vehicle_spawn: unsafe extern "C" fn(*mut VehicleHandler, *mut IVehicle),
84    pub on_unoccupied_vehicle_update:
85        unsafe extern "C" fn(*mut VehicleHandler, *mut IVehicle, *mut u8, *const u8) -> bool,
86    pub on_trailer_update:
87        unsafe extern "C" fn(*mut VehicleHandler, *mut u8, *mut IVehicle) -> bool,
88    pub on_vehicle_siren_state_change:
89        unsafe extern "C" fn(*mut VehicleHandler, *mut u8, *mut IVehicle, u8) -> bool,
90}
91
92/// `VehicleEventHandler` vtable — MSVC ABI (`this` in ECX).
93#[cfg(target_env = "msvc")]
94#[repr(C)]
95pub struct VehicleHandlerVTable {
96    pub on_vehicle_stream_in:
97        unsafe extern "thiscall" fn(*mut VehicleHandler, *mut IVehicle, *mut u8),
98    pub on_vehicle_stream_out:
99        unsafe extern "thiscall" fn(*mut VehicleHandler, *mut IVehicle, *mut u8),
100    pub on_vehicle_death: unsafe extern "thiscall" fn(*mut VehicleHandler, *mut IVehicle, *mut u8),
101    pub on_player_enter_vehicle:
102        unsafe extern "thiscall" fn(*mut VehicleHandler, *mut u8, *mut IVehicle, bool),
103    pub on_player_exit_vehicle:
104        unsafe extern "thiscall" fn(*mut VehicleHandler, *mut u8, *mut IVehicle),
105    pub on_vehicle_damage_status_update:
106        unsafe extern "thiscall" fn(*mut VehicleHandler, *mut IVehicle, *mut u8),
107    pub on_vehicle_paint_job:
108        unsafe extern "thiscall" fn(*mut VehicleHandler, *mut u8, *mut IVehicle, i32) -> bool,
109    pub on_vehicle_mod:
110        unsafe extern "thiscall" fn(*mut VehicleHandler, *mut u8, *mut IVehicle, i32) -> bool,
111    pub on_vehicle_respray:
112        unsafe extern "thiscall" fn(*mut VehicleHandler, *mut u8, *mut IVehicle, i32, i32) -> bool,
113    pub on_enter_exit_mod_shop:
114        unsafe extern "thiscall" fn(*mut VehicleHandler, *mut u8, bool, i32),
115    pub on_vehicle_spawn: unsafe extern "thiscall" fn(*mut VehicleHandler, *mut IVehicle),
116    pub on_unoccupied_vehicle_update:
117        unsafe extern "thiscall" fn(*mut VehicleHandler, *mut IVehicle, *mut u8, *const u8) -> bool,
118    pub on_trailer_update:
119        unsafe extern "thiscall" fn(*mut VehicleHandler, *mut u8, *mut IVehicle) -> bool,
120    pub on_vehicle_siren_state_change:
121        unsafe extern "thiscall" fn(*mut VehicleHandler, *mut u8, *mut IVehicle, u8) -> bool,
122}
123
124/// Object the server calls on vehicle events. The `*mut u8` arguments are
125/// `IPlayer*`; casting them to [`super::players::IPlayer`] is the caller's
126/// call, and keeps this module from depending on the player one.
127#[repr(C)]
128pub struct VehicleHandler {
129    vtable: *const VehicleHandlerVTable,
130}
131
132// SAFETY: the handler is only ever touched on the server's main thread.
133unsafe impl Send for VehicleHandler {}
134unsafe impl Sync for VehicleHandler {}
135
136impl VehicleHandler {
137    /// Builds a handler backed by `vtable`.
138    #[must_use]
139    pub fn new(vtable: *const VehicleHandlerVTable) -> Self {
140        Self { vtable }
141    }
142}
143
144virtual_fns! {
145    /// `IVehiclesComponent::getEventDispatcher()`.
146    ///
147    /// # Safety
148    /// `component` must be a live `IVehiclesComponent`.
149    #[must_use]
150    pub fn vehicle_event_dispatcher(component: IVehiclesComponent) -> *mut IVehicleDispatcher = [0, SLOT_VEHICLE_DISPATCHER] or std::ptr::null_mut();
151}
152
153/// Registers `handler` on the vehicle dispatcher (`addEventHandler`, slot [0]).
154///
155/// # Safety
156/// Both pointers must be valid, and `handler` must outlive the registration.
157pub unsafe fn add_vehicle_handler(
158    dispatcher: *mut IVehicleDispatcher,
159    handler: *mut VehicleHandler,
160) -> bool {
161    call_vtable!(
162        dispatcher.cast::<u8>(),
163        0,
164        0,
165        (*mut VehicleHandler, i8) -> bool,
166        (handler, 0),
167        false
168    )
169}
170
171virtual_fns! {
172    /// `IReadOnlyPool<IVehicle>::get(int)` — the vehicle with that id, or null.
173    ///
174    /// # Safety
175    /// `component` must be a live `IVehiclesComponent`.
176    #[must_use]
177    pub fn vehicle_by_id(component: IVehiclesComponent, id: i32) -> *mut IVehicle = [VEHICLE_POOL_OFFSET, super::players::SLOT_POOL_GET] or std::ptr::null_mut();
178
179    /// `IEntity::getID()` for a vehicle — the id Pawn scripts use.
180    ///
181    /// # Safety
182    /// See [`vehicle_model`].
183    #[must_use]
184    pub fn vehicle_id(vehicle: IVehicle) -> i32 = [ENTITY_OFFSET, super::players::SLOT_ENTITY_GET_ID] or -1;
185}
186
187/// Casts a component handle obtained by UID into the vehicles component.
188///
189/// # Safety
190/// `component` must be what `queryComponent(VEHICLES_COMPONENT_UID)` returned.
191#[must_use]
192#[deprecated(
193    since = "3.6.0",
194    note = "the UID and the cast can disagree; use `omp_query::<Component<IVehiclesComponent>>()` and `Component::as_ptr`"
195)]
196pub unsafe fn as_vehicles_component(component: *mut ServerComponent) -> *mut IVehiclesComponent {
197    component.cast::<IVehiclesComponent>()
198}
199
200/// `IVehiclesComponent::create(...)` — spawns a vehicle.
201///
202/// `respawn_delay` is in seconds; a negative value means "never respawn", which
203/// is the server's own default. `colour1`/`colour2` of `-1` ask the server to
204/// pick from the model's palette.
205///
206/// Returns null when the component pointer is unusable or the server refuses
207/// (an unknown model, or the vehicle pool being full).
208///
209/// # Safety
210/// `component` must be a live `IVehiclesComponent`.
211#[must_use]
212// Mirrors `IVehiclesComponent::create` argument for argument. Grouping them
213// into a struct would read better in isolation and worse here: the call has to
214// be checked against the C++ declaration, and a one-to-one mapping is what
215// makes that check possible.
216#[allow(clippy::too_many_arguments)]
217pub unsafe fn create_vehicle(
218    component: *mut IVehiclesComponent,
219    model: i32,
220    position: Vector3,
221    z_angle: f32,
222    colour1: i32,
223    colour2: i32,
224    respawn_delay_secs: i64,
225    siren: bool,
226) -> *mut IVehicle {
227    call_vtable!(
228        component.cast::<u8>(),
229        0,
230        SLOT_CREATE_VEHICLE,
231        (bool, i32, Vector3, f32, i32, i32, i64, bool) -> *mut IVehicle,
232        (false, model, position, z_angle, colour1, colour2, respawn_delay_secs, siren),
233        std::ptr::null_mut()
234    )
235}
236
237virtual_fns! {
238    /// `IVehicle::getModel()`.
239    ///
240    /// # Safety
241    /// `vehicle` must come from [`create_vehicle`] and still exist.
242    #[must_use]
243    pub fn vehicle_model(vehicle: IVehicle) -> i32 = [0, SLOT_VEHICLE_GET_MODEL] or 0;
244
245    /// `IVehicle::getHealth()`.
246    ///
247    /// # Safety
248    /// See [`vehicle_model`].
249    #[must_use]
250    pub fn vehicle_health(vehicle: IVehicle) -> f32 = [0, SLOT_VEHICLE_GET_HEALTH] or 0.0;
251
252    /// `IVehicle::setHealth(float)`.
253    ///
254    /// # Safety
255    /// See [`vehicle_model`].
256    pub fn vehicle_set_health(vehicle: IVehicle, health: f32) = [0, SLOT_VEHICLE_SET_HEALTH];
257
258    /// `IVehicle::setColour(int, int)`.
259    ///
260    /// # Safety
261    /// See [`vehicle_model`].
262    pub fn vehicle_set_colour(vehicle: IVehicle, colour1: i32, colour2: i32) = [0, SLOT_VEHICLE_SET_COLOUR];
263
264    /// `IEntity::getPosition()` for a vehicle.
265    ///
266    /// `IVehicle` carries the same `IEntity` subobject a player does, at the same
267    /// offset, so this is the player accessor with a different handle.
268    ///
269    /// # Safety
270    /// See [`vehicle_model`].
271    #[must_use]
272    pub fn vehicle_position(vehicle: IVehicle) -> Vector3 = [ENTITY_OFFSET, SLOT_ENTITY_GET_POSITION] or Vector3::ZERO;
273}
274
275// Each interface knows its own UID, so `omp_query::<Component<I>>()` finds it.
276
277impl ComponentInterface for IVehiclesComponent {
278    const UID: UID = VEHICLES_COMPONENT_UID;
279}
280
281#[cfg(test)]
282mod tests {
283    use super::*;
284
285    #[test]
286    fn uid_matches_the_header() {
287        // `VehicleComponent_UID` in `vehicles.hpp`.
288        assert_eq!(VEHICLES_COMPONENT_UID, 0x3f1f_62ee_9e22_ab19);
289    }
290
291    #[test]
292    fn slots_match_what_clang_reports() {
293        // `cargo xtask vtable IVehiclesComponent` / `IVehicle`. The `create`
294        // pair is overloaded, so MSVC emits it reversed: [18] is the
295        // eight-argument overload there, [17] the `VehicleSpawnData` one.
296        #[cfg(not(target_env = "msvc"))]
297        let expected = [19, 11, 13, 14, 57];
298        #[cfg(target_env = "msvc")]
299        let expected = [18, 10, 12, 13, 56];
300        assert_eq!(
301            [
302                SLOT_CREATE_VEHICLE,
303                SLOT_VEHICLE_SET_COLOUR,
304                SLOT_VEHICLE_SET_HEALTH,
305                SLOT_VEHICLE_GET_HEALTH,
306                SLOT_VEHICLE_GET_MODEL,
307            ],
308            expected
309        );
310    }
311
312    #[test]
313    fn a_null_component_creates_nothing() {
314        let position = Vector3::ZERO;
315        let vehicle =
316            unsafe { create_vehicle(std::ptr::null_mut(), 411, position, 0.0, -1, -1, -1, false) };
317        assert!(vehicle.is_null());
318    }
319}