1use super::players::{ENTITY_OFFSET, SLOT_ENTITY_GET_POSITION};
26use super::server::ServerComponent;
27use super::types::{UID, Vector3};
28use super::vtable::call_vtable;
29
30pub 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#[cfg(not(target_env = "msvc"))]
60const SLOT_VEHICLE_DISPATCHER: usize = 21;
61#[cfg(target_env = "msvc")]
62const SLOT_VEHICLE_DISPATCHER: usize = 19;
63
64#[cfg(not(target_env = "msvc"))]
70const VEHICLE_POOL_OFFSET: isize = 44;
71#[cfg(target_env = "msvc")]
72const VEHICLE_POOL_OFFSET: isize = 64;
73
74#[repr(C)]
76pub struct IVehiclesComponent {
77 _opaque: [u8; 0],
78}
79
80#[repr(C)]
82pub struct IVehicle {
83 _opaque: [u8; 0],
84}
85
86#[repr(C)]
88pub struct IVehicleDispatcher {
89 _opaque: [u8; 0],
90}
91
92#[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#[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#[repr(C)]
161pub struct VehicleHandler {
162 vtable: *const VehicleHandlerVTable,
163}
164
165unsafe impl Send for VehicleHandler {}
167unsafe impl Sync for VehicleHandler {}
168
169impl VehicleHandler {
170 #[must_use]
172 pub fn new(vtable: *const VehicleHandlerVTable) -> Self {
173 Self { vtable }
174 }
175}
176
177#[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
195pub 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#[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#[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#[must_use]
250pub unsafe fn as_vehicles_component(component: *mut ServerComponent) -> *mut IVehiclesComponent {
251 component.cast::<IVehiclesComponent>()
252}
253
254#[must_use]
266#[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
291unsafe fn vehicle_f32(vehicle: *mut IVehicle, slot: usize) -> f32 {
293 call_vtable!(vehicle.cast::<u8>(), 0, slot, () -> f32, (), 0.0)
294}
295
296#[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#[must_use]
310pub unsafe fn vehicle_health(vehicle: *mut IVehicle) -> f32 {
311 unsafe { vehicle_f32(vehicle, SLOT_VEHICLE_GET_HEALTH) }
312}
313
314pub 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
329pub 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#[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 assert_eq!(VEHICLES_COMPONENT_UID, 0x3f1f_62ee_9e22_ab19);
376 }
377
378 #[test]
379 fn slots_match_what_clang_reports() {
380 #[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}