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 `scripts/omp-vtable.py` reports it
23//! without anyone having to remember the rule.
24
25use super::players::{ENTITY_OFFSET, SLOT_ENTITY_GET_POSITION};
26use super::server::ServerComponent;
27use super::types::{UID, Vector3};
28use super::vtable::call_vtable;
29
30/// UID of the Open Multiplayer `Vehicles` component.
31pub const VEHICLES_COMPONENT_UID: UID = 0x3f1f_62ee_9e22_ab19;
32
33#[cfg(not(target_env = "msvc"))]
34const SLOT_CREATE_VEHICLE: usize = 19;
35#[cfg(target_env = "msvc")]
36const SLOT_CREATE_VEHICLE: usize = 18;
37
38#[cfg(not(target_env = "msvc"))]
39const SLOT_VEHICLE_SET_COLOUR: usize = 11;
40#[cfg(target_env = "msvc")]
41const SLOT_VEHICLE_SET_COLOUR: usize = 10;
42
43#[cfg(not(target_env = "msvc"))]
44const SLOT_VEHICLE_SET_HEALTH: usize = 13;
45#[cfg(target_env = "msvc")]
46const SLOT_VEHICLE_SET_HEALTH: usize = 12;
47
48#[cfg(not(target_env = "msvc"))]
49const SLOT_VEHICLE_GET_HEALTH: usize = 14;
50#[cfg(target_env = "msvc")]
51const SLOT_VEHICLE_GET_HEALTH: usize = 13;
52
53#[cfg(not(target_env = "msvc"))]
54const SLOT_VEHICLE_GET_MODEL: usize = 57;
55#[cfg(target_env = "msvc")]
56const SLOT_VEHICLE_GET_MODEL: usize = 56;
57
58/// Slot of `IVehiclesComponent::getEventDispatcher()`.
59#[cfg(not(target_env = "msvc"))]
60const SLOT_VEHICLE_DISPATCHER: usize = 21;
61#[cfg(target_env = "msvc")]
62const SLOT_VEHICLE_DISPATCHER: usize = 19;
63
64/// Offset of the `IReadOnlyPool<IVehicle>` subobject inside the component.
65///
66/// Larger than the player pool's because `IVehiclesComponent` reaches it
67/// through `IComponent`, which carries an `IUIDProvider` of its own. From
68/// clang's record layout.
69#[cfg(not(target_env = "msvc"))]
70const VEHICLE_POOL_OFFSET: isize = 44;
71#[cfg(target_env = "msvc")]
72const VEHICLE_POOL_OFFSET: isize = 64;
73
74/// Opaque handle for the server's `IVehiclesComponent*`.
75#[repr(C)]
76pub struct IVehiclesComponent {
77    _opaque: [u8; 0],
78}
79
80/// Opaque handle for the server's `IVehicle*`.
81#[repr(C)]
82pub struct IVehicle {
83    _opaque: [u8; 0],
84}
85
86/// Opaque handle for `IEventDispatcher<VehicleEventHandler>*`.
87#[repr(C)]
88pub struct IVehicleDispatcher {
89    _opaque: [u8; 0],
90}
91
92/// `VehicleEventHandler` vtable — Itanium ABI.
93///
94/// Fourteen slots, in the order `vehicles.hpp` declares them. No virtual
95/// destructor, so MSVC numbers them the same; only the calling convention
96/// differs. The handlers returning `bool` can refuse the action: a `false` from
97/// `on_vehicle_paint_job`, `on_vehicle_mod` or `on_vehicle_respray` rejects it.
98#[cfg(not(target_env = "msvc"))]
99#[repr(C)]
100pub struct VehicleHandlerVTable {
101    pub on_vehicle_stream_in: unsafe extern "C" fn(*mut VehicleHandler, *mut IVehicle, *mut u8),
102    pub on_vehicle_stream_out: unsafe extern "C" fn(*mut VehicleHandler, *mut IVehicle, *mut u8),
103    pub on_vehicle_death: unsafe extern "C" fn(*mut VehicleHandler, *mut IVehicle, *mut u8),
104    pub on_player_enter_vehicle:
105        unsafe extern "C" fn(*mut VehicleHandler, *mut u8, *mut IVehicle, bool),
106    pub on_player_exit_vehicle: unsafe extern "C" fn(*mut VehicleHandler, *mut u8, *mut IVehicle),
107    pub on_vehicle_damage_status_update:
108        unsafe extern "C" fn(*mut VehicleHandler, *mut IVehicle, *mut u8),
109    pub on_vehicle_paint_job:
110        unsafe extern "C" fn(*mut VehicleHandler, *mut u8, *mut IVehicle, i32) -> bool,
111    pub on_vehicle_mod:
112        unsafe extern "C" fn(*mut VehicleHandler, *mut u8, *mut IVehicle, i32) -> bool,
113    pub on_vehicle_respray:
114        unsafe extern "C" fn(*mut VehicleHandler, *mut u8, *mut IVehicle, i32, i32) -> bool,
115    pub on_enter_exit_mod_shop: unsafe extern "C" fn(*mut VehicleHandler, *mut u8, bool, i32),
116    pub on_vehicle_spawn: unsafe extern "C" fn(*mut VehicleHandler, *mut IVehicle),
117    pub on_unoccupied_vehicle_update:
118        unsafe extern "C" fn(*mut VehicleHandler, *mut IVehicle, *mut u8, *const u8) -> bool,
119    pub on_trailer_update:
120        unsafe extern "C" fn(*mut VehicleHandler, *mut u8, *mut IVehicle) -> bool,
121    pub on_vehicle_siren_state_change:
122        unsafe extern "C" fn(*mut VehicleHandler, *mut u8, *mut IVehicle, u8) -> bool,
123}
124
125/// `VehicleEventHandler` vtable — MSVC ABI (`this` in ECX).
126#[cfg(target_env = "msvc")]
127#[repr(C)]
128pub struct VehicleHandlerVTable {
129    pub on_vehicle_stream_in:
130        unsafe extern "thiscall" fn(*mut VehicleHandler, *mut IVehicle, *mut u8),
131    pub on_vehicle_stream_out:
132        unsafe extern "thiscall" fn(*mut VehicleHandler, *mut IVehicle, *mut u8),
133    pub on_vehicle_death: unsafe extern "thiscall" fn(*mut VehicleHandler, *mut IVehicle, *mut u8),
134    pub on_player_enter_vehicle:
135        unsafe extern "thiscall" fn(*mut VehicleHandler, *mut u8, *mut IVehicle, bool),
136    pub on_player_exit_vehicle:
137        unsafe extern "thiscall" fn(*mut VehicleHandler, *mut u8, *mut IVehicle),
138    pub on_vehicle_damage_status_update:
139        unsafe extern "thiscall" fn(*mut VehicleHandler, *mut IVehicle, *mut u8),
140    pub on_vehicle_paint_job:
141        unsafe extern "thiscall" fn(*mut VehicleHandler, *mut u8, *mut IVehicle, i32) -> bool,
142    pub on_vehicle_mod:
143        unsafe extern "thiscall" fn(*mut VehicleHandler, *mut u8, *mut IVehicle, i32) -> bool,
144    pub on_vehicle_respray:
145        unsafe extern "thiscall" fn(*mut VehicleHandler, *mut u8, *mut IVehicle, i32, i32) -> bool,
146    pub on_enter_exit_mod_shop:
147        unsafe extern "thiscall" fn(*mut VehicleHandler, *mut u8, bool, i32),
148    pub on_vehicle_spawn: unsafe extern "thiscall" fn(*mut VehicleHandler, *mut IVehicle),
149    pub on_unoccupied_vehicle_update:
150        unsafe extern "thiscall" fn(*mut VehicleHandler, *mut IVehicle, *mut u8, *const u8) -> bool,
151    pub on_trailer_update:
152        unsafe extern "thiscall" fn(*mut VehicleHandler, *mut u8, *mut IVehicle) -> bool,
153    pub on_vehicle_siren_state_change:
154        unsafe extern "thiscall" fn(*mut VehicleHandler, *mut u8, *mut IVehicle, u8) -> bool,
155}
156
157/// Object the server calls on vehicle events. The `*mut u8` arguments are
158/// `IPlayer*`; casting them to [`super::players::IPlayer`] is the caller's
159/// call, and keeps this module from depending on the player one.
160#[repr(C)]
161pub struct VehicleHandler {
162    vtable: *const VehicleHandlerVTable,
163}
164
165// SAFETY: the handler is only ever touched on the server's main thread.
166unsafe impl Send for VehicleHandler {}
167unsafe impl Sync for VehicleHandler {}
168
169impl VehicleHandler {
170    /// Builds a handler backed by `vtable`.
171    #[must_use]
172    pub fn new(vtable: *const VehicleHandlerVTable) -> Self {
173        Self { vtable }
174    }
175}
176
177/// `IVehiclesComponent::getEventDispatcher()`.
178///
179/// # Safety
180/// `component` must be a live `IVehiclesComponent`.
181#[must_use]
182pub unsafe fn vehicle_event_dispatcher(
183    component: *mut IVehiclesComponent,
184) -> *mut IVehicleDispatcher {
185    call_vtable!(
186        component.cast::<u8>(),
187        0,
188        SLOT_VEHICLE_DISPATCHER,
189        () -> *mut IVehicleDispatcher,
190        (),
191        std::ptr::null_mut()
192    )
193}
194
195/// Registers `handler` on the vehicle dispatcher (`addEventHandler`, slot [0]).
196///
197/// # Safety
198/// Both pointers must be valid, and `handler` must outlive the registration.
199pub unsafe fn add_vehicle_handler(
200    dispatcher: *mut IVehicleDispatcher,
201    handler: *mut VehicleHandler,
202) -> bool {
203    call_vtable!(
204        dispatcher.cast::<u8>(),
205        0,
206        0,
207        (*mut VehicleHandler, i8) -> bool,
208        (handler, 0),
209        false
210    )
211}
212
213/// `IReadOnlyPool<IVehicle>::get(int)` — the vehicle with that id, or null.
214///
215/// # Safety
216/// `component` must be a live `IVehiclesComponent`.
217#[must_use]
218pub unsafe fn vehicle_by_id(component: *mut IVehiclesComponent, id: i32) -> *mut IVehicle {
219    call_vtable!(
220        component.cast::<u8>(),
221        VEHICLE_POOL_OFFSET,
222        super::players::SLOT_POOL_GET_PUB,
223        (i32) -> *mut IVehicle,
224        (id),
225        std::ptr::null_mut()
226    )
227}
228
229/// `IEntity::getID()` for a vehicle — the id Pawn scripts use.
230///
231/// # Safety
232/// See [`vehicle_model`].
233#[must_use]
234pub unsafe fn vehicle_id(vehicle: *mut IVehicle) -> i32 {
235    call_vtable!(
236        vehicle.cast::<u8>(),
237        ENTITY_OFFSET,
238        super::players::SLOT_ENTITY_GET_ID,
239        () -> i32,
240        (),
241        -1
242    )
243}
244
245/// Casts a component handle obtained by UID into the vehicles component.
246///
247/// # Safety
248/// `component` must be what `queryComponent(VEHICLES_COMPONENT_UID)` returned.
249#[must_use]
250pub unsafe fn as_vehicles_component(component: *mut ServerComponent) -> *mut IVehiclesComponent {
251    component.cast::<IVehiclesComponent>()
252}
253
254/// `IVehiclesComponent::create(...)` — spawns a vehicle.
255///
256/// `respawn_delay` is in seconds; a negative value means "never respawn", which
257/// is the server's own default. `colour1`/`colour2` of `-1` ask the server to
258/// pick from the model's palette.
259///
260/// Returns null when the component pointer is unusable or the server refuses
261/// (an unknown model, or the vehicle pool being full).
262///
263/// # Safety
264/// `component` must be a live `IVehiclesComponent`.
265#[must_use]
266// Mirrors `IVehiclesComponent::create` argument for argument. Grouping them
267// into a struct would read better in isolation and worse here: the call has to
268// be checked against the C++ declaration, and a one-to-one mapping is what
269// makes that check possible.
270#[allow(clippy::too_many_arguments)]
271pub unsafe fn create_vehicle(
272    component: *mut IVehiclesComponent,
273    model: i32,
274    position: Vector3,
275    z_angle: f32,
276    colour1: i32,
277    colour2: i32,
278    respawn_delay_secs: i64,
279    siren: bool,
280) -> *mut IVehicle {
281    call_vtable!(
282        component.cast::<u8>(),
283        0,
284        SLOT_CREATE_VEHICLE,
285        (bool, i32, Vector3, f32, i32, i32, i64, bool) -> *mut IVehicle,
286        (false, model, position, z_angle, colour1, colour2, respawn_delay_secs, siren),
287        std::ptr::null_mut()
288    )
289}
290
291/// Reads a `f32` getter that takes no arguments from the vehicle's vtable.
292unsafe fn vehicle_f32(vehicle: *mut IVehicle, slot: usize) -> f32 {
293    call_vtable!(vehicle.cast::<u8>(), 0, slot, () -> f32, (), 0.0)
294}
295
296/// `IVehicle::getModel()`.
297///
298/// # Safety
299/// `vehicle` must come from [`create_vehicle`] and still exist.
300#[must_use]
301pub unsafe fn vehicle_model(vehicle: *mut IVehicle) -> i32 {
302    call_vtable!(vehicle.cast::<u8>(), 0, SLOT_VEHICLE_GET_MODEL, () -> i32, (), 0)
303}
304
305/// `IVehicle::getHealth()`.
306///
307/// # Safety
308/// See [`vehicle_model`].
309#[must_use]
310pub unsafe fn vehicle_health(vehicle: *mut IVehicle) -> f32 {
311    unsafe { vehicle_f32(vehicle, SLOT_VEHICLE_GET_HEALTH) }
312}
313
314/// `IVehicle::setHealth(float)`.
315///
316/// # Safety
317/// See [`vehicle_model`].
318pub unsafe fn vehicle_set_health(vehicle: *mut IVehicle, health: f32) {
319    call_vtable!(
320        vehicle.cast::<u8>(),
321        0,
322        SLOT_VEHICLE_SET_HEALTH,
323        (f32) -> (),
324        (health),
325        ()
326    )
327}
328
329/// `IVehicle::setColour(int, int)`.
330///
331/// # Safety
332/// See [`vehicle_model`].
333pub unsafe fn vehicle_set_colour(vehicle: *mut IVehicle, colour1: i32, colour2: i32) {
334    call_vtable!(
335        vehicle.cast::<u8>(),
336        0,
337        SLOT_VEHICLE_SET_COLOUR,
338        (i32, i32) -> (),
339        (colour1, colour2),
340        ()
341    )
342}
343
344/// `IEntity::getPosition()` for a vehicle.
345///
346/// `IVehicle` carries the same `IEntity` subobject a player does, at the same
347/// offset, so this is the player accessor with a different handle.
348///
349/// # Safety
350/// See [`vehicle_model`].
351#[must_use]
352pub unsafe fn vehicle_position(vehicle: *mut IVehicle) -> Vector3 {
353    let zero = Vector3 {
354        x: 0.0,
355        y: 0.0,
356        z: 0.0,
357    };
358    call_vtable!(
359        vehicle.cast::<u8>(),
360        ENTITY_OFFSET,
361        SLOT_ENTITY_GET_POSITION,
362        () -> Vector3,
363        (),
364        zero
365    )
366}
367
368#[cfg(test)]
369mod tests {
370    use super::*;
371
372    #[test]
373    fn uid_matches_the_header() {
374        // `VehicleComponent_UID` in `vehicles.hpp`.
375        assert_eq!(VEHICLES_COMPONENT_UID, 0x3f1f_62ee_9e22_ab19);
376    }
377
378    #[test]
379    fn slots_match_what_clang_reports() {
380        // `scripts/omp-vtable.py IVehiclesComponent` / `IVehicle`. The `create`
381        // pair is overloaded, so MSVC emits it reversed: [18] is the
382        // eight-argument overload there, [17] the `VehicleSpawnData` one.
383        #[cfg(not(target_env = "msvc"))]
384        let expected = [19, 11, 13, 14, 57];
385        #[cfg(target_env = "msvc")]
386        let expected = [18, 10, 12, 13, 56];
387        assert_eq!(
388            [
389                SLOT_CREATE_VEHICLE,
390                SLOT_VEHICLE_SET_COLOUR,
391                SLOT_VEHICLE_SET_HEALTH,
392                SLOT_VEHICLE_GET_HEALTH,
393                SLOT_VEHICLE_GET_MODEL,
394            ],
395            expected
396        );
397    }
398
399    #[test]
400    fn a_null_component_creates_nothing() {
401        let position = Vector3 {
402            x: 0.0,
403            y: 0.0,
404            z: 0.0,
405        };
406        let vehicle =
407            unsafe { create_vehicle(std::ptr::null_mut(), 411, position, 0.0, -1, -1, -1, false) };
408        assert!(vehicle.is_null());
409    }
410}