1use super::component_api::ComponentInterface;
18use super::players::{
19 ENTITY_OFFSET, SLOT_ENTITY_GET_ID, SLOT_ENTITY_GET_ROTATION, SLOT_ENTITY_SET_ROTATION,
20};
21use super::server::ServerComponent;
22use super::types::{Colour, GTAQuat, StringView, UID, Vector2, Vector3};
23use super::vtable::{call_vtable, opaque, slots, virtual_fns};
24
25pub const TEXTDRAWS_COMPONENT_UID: UID = 0x9b5d_c2b1_d15c_992a;
27
28pub const GANGZONES_COMPONENT_UID: UID = 0xb335_1d11_ee8d_8056;
30
31pub const ACTORS_COMPONENT_UID: UID = 0xc81c_a021_eae2_ad5c;
33
34pub const TEXTLABELS_COMPONENT_UID: UID = 0xa0c5_7ea8_0a00_9742;
36
37pub const MENUS_COMPONENT_UID: UID = 0x621e_219e_b97e_e0b2;
39
40pub const CLASSES_COMPONENT_UID: UID = 0x8cfb_3183_976d_a208;
42
43pub const OBJECTS_COMPONENT_UID: UID = 0x59f8_415f_72da_6160;
45
46pub const PICKUPS_COMPONENT_UID: UID = 0xcf30_4faa_363d_d971;
48
49slots! {
50 SLOT_CREATE_OBJECT: usize = 21, 19;
51 SLOT_CREATE_PICKUP: usize = 19, 17;
52 SLOT_CREATE_TEXTDRAW: usize = 19, 18;
55 SLOT_CREATE_GANGZONE: usize = 19, 17;
56 SLOT_CREATE_ACTOR: usize = 19, 17;
57 SLOT_CREATE_TEXTLABEL: usize = 18, 18;
61 SLOT_CREATE_MENU: usize = 19, 17;
62 SLOT_CREATE_CLASS: usize = 19, 17;
63}
64
65pub const MAX_WEAPON_SLOTS: usize = 13;
67
68#[repr(C)]
70#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
71pub struct WeaponSlot {
72 pub id: u8,
73 pub ammo: u32,
74}
75
76opaque! {
77 pub ITextLabelsComponent;
79 pub ITextLabel;
81 pub IMenusComponent;
83 pub IMenu;
85 pub IClassesComponent;
87 pub IClass;
89 pub ITextDrawsComponent;
91 pub ITextDraw;
93 pub IGangZonesComponent;
95 pub IGangZone;
97 pub IActorsComponent;
99 pub IActor;
101}
102
103#[repr(C)]
105#[derive(Debug, Clone, Copy, PartialEq, Default)]
106pub struct GangZonePos {
107 pub min: Vector2,
108 pub max: Vector2,
109}
110
111opaque! {
112 pub IObjectsComponent;
114 pub IPickupsComponent;
116 pub IPickup;
118}
119
120pub type PickupType = i32;
124
125#[must_use]
130#[deprecated(
131 since = "3.6.0",
132 note = "the UID and the cast can disagree; use `omp_query::<Component<IObjectsComponent>>()` and `Component::as_ptr`"
133)]
134pub unsafe fn as_objects_component(component: *mut ServerComponent) -> *mut IObjectsComponent {
135 component.cast::<IObjectsComponent>()
136}
137
138#[must_use]
143#[deprecated(
144 since = "3.6.0",
145 note = "the UID and the cast can disagree; use `omp_query::<Component<IPickupsComponent>>()` and `Component::as_ptr`"
146)]
147pub unsafe fn as_pickups_component(component: *mut ServerComponent) -> *mut IPickupsComponent {
148 component.cast::<IPickupsComponent>()
149}
150
151virtual_fns! {
152 #[must_use]
160 pub fn create_object(component: IObjectsComponent, model: i32, position: Vector3, rotation: Vector3, draw_distance: f32) -> *mut super::players::IObject = [0, SLOT_CREATE_OBJECT] or std::ptr::null_mut();
161
162 #[must_use]
167 pub fn create_pickup(component: IPickupsComponent, model: i32, pickup_type: PickupType, position: Vector3, virtual_world: u32, is_static: bool) -> *mut IPickup = [0, SLOT_CREATE_PICKUP] or std::ptr::null_mut();
168}
169
170#[must_use]
175#[deprecated(
176 since = "3.6.0",
177 note = "the UID and the cast can disagree; use `omp_query::<Component<ITextDrawsComponent>>()` and `Component::as_ptr`"
178)]
179pub unsafe fn as_textdraws_component(component: *mut ServerComponent) -> *mut ITextDrawsComponent {
180 component.cast::<ITextDrawsComponent>()
181}
182
183#[must_use]
188#[deprecated(
189 since = "3.6.0",
190 note = "the UID and the cast can disagree; use `omp_query::<Component<IGangZonesComponent>>()` and `Component::as_ptr`"
191)]
192pub unsafe fn as_gangzones_component(component: *mut ServerComponent) -> *mut IGangZonesComponent {
193 component.cast::<IGangZonesComponent>()
194}
195
196#[must_use]
201#[deprecated(
202 since = "3.6.0",
203 note = "the UID and the cast can disagree; use `omp_query::<Component<IActorsComponent>>()` and `Component::as_ptr`"
204)]
205pub unsafe fn as_actors_component(component: *mut ServerComponent) -> *mut IActorsComponent {
206 component.cast::<IActorsComponent>()
207}
208
209#[must_use]
216pub unsafe fn create_textdraw(
217 component: *mut ITextDrawsComponent,
218 position: Vector2,
219 text: &str,
220) -> *mut ITextDraw {
221 let text = StringView::of(text);
222 call_vtable!(
223 component.cast::<u8>(),
224 0,
225 SLOT_CREATE_TEXTDRAW,
226 (Vector2, StringView) -> *mut ITextDraw,
227 (position, text),
228 std::ptr::null_mut()
229 )
230}
231
232virtual_fns! {
233 #[must_use]
238 pub fn create_gangzone(component: IGangZonesComponent, area: GangZonePos) -> *mut IGangZone = [0, SLOT_CREATE_GANGZONE] or std::ptr::null_mut();
239
240 #[must_use]
245 pub fn create_actor(component: IActorsComponent, skin: i32, position: Vector3, angle: f32) -> *mut IActor = [0, SLOT_CREATE_ACTOR] or std::ptr::null_mut();
246}
247
248#[must_use]
253#[deprecated(
254 since = "3.6.0",
255 note = "the UID and the cast can disagree; use `omp_query::<Component<ITextLabelsComponent>>()` and `Component::as_ptr`"
256)]
257pub unsafe fn as_textlabels_component(
258 component: *mut ServerComponent,
259) -> *mut ITextLabelsComponent {
260 component.cast::<ITextLabelsComponent>()
261}
262
263#[must_use]
268#[deprecated(
269 since = "3.6.0",
270 note = "the UID and the cast can disagree; use `omp_query::<Component<IMenusComponent>>()` and `Component::as_ptr`"
271)]
272pub unsafe fn as_menus_component(component: *mut ServerComponent) -> *mut IMenusComponent {
273 component.cast::<IMenusComponent>()
274}
275
276#[must_use]
281#[deprecated(
282 since = "3.6.0",
283 note = "the UID and the cast can disagree; use `omp_query::<Component<IClassesComponent>>()` and `Component::as_ptr`"
284)]
285pub unsafe fn as_classes_component(component: *mut ServerComponent) -> *mut IClassesComponent {
286 component.cast::<IClassesComponent>()
287}
288
289#[must_use]
297#[allow(clippy::too_many_arguments)]
298pub unsafe fn create_textlabel(
299 component: *mut ITextLabelsComponent,
300 text: &str,
301 colour: Colour,
302 position: Vector3,
303 draw_distance: f32,
304 virtual_world: i32,
305 line_of_sight: bool,
306) -> *mut ITextLabel {
307 let text = StringView::of(text);
308 call_vtable!(
309 component.cast::<u8>(),
310 0,
311 SLOT_CREATE_TEXTLABEL,
312 (StringView, Colour, Vector3, f32, i32, bool) -> *mut ITextLabel,
313 (text, colour, position, draw_distance, virtual_world, line_of_sight),
314 std::ptr::null_mut()
315 )
316}
317
318#[must_use]
323pub unsafe fn create_menu(
324 component: *mut IMenusComponent,
325 title: &str,
326 position: Vector2,
327 columns: u8,
328 column1_width: f32,
329 column2_width: f32,
330) -> *mut IMenu {
331 let title = StringView::of(title);
332 call_vtable!(
333 component.cast::<u8>(),
334 0,
335 SLOT_CREATE_MENU,
336 (StringView, Vector2, u8, f32, f32) -> *mut IMenu,
337 (title, position, columns, column1_width, column2_width),
338 std::ptr::null_mut()
339 )
340}
341
342#[must_use]
351pub unsafe fn create_class(
352 component: *mut IClassesComponent,
353 skin: i32,
354 team: i32,
355 spawn: Vector3,
356 angle: f32,
357 weapons: &[WeaponSlot; MAX_WEAPON_SLOTS],
358) -> *mut IClass {
359 call_vtable!(
360 component.cast::<u8>(),
361 0,
362 SLOT_CREATE_CLASS,
363 (i32, i32, Vector3, f32, *const WeaponSlot) -> *mut IClass,
364 (skin, team, spawn, angle, weapons.as_ptr()),
365 std::ptr::null_mut()
366 )
367}
368
369virtual_fns! {
370 #[must_use]
377 pub fn entity_id(entity: u8) -> i32 = [ENTITY_OFFSET, SLOT_ENTITY_GET_ID] or -1;
378
379 #[must_use]
384 pub fn entity_rotation(entity: u8) -> GTAQuat = [ENTITY_OFFSET, SLOT_ENTITY_GET_ROTATION] or GTAQuat::IDENTITY;
385
386 pub fn entity_set_rotation(entity: u8, rotation: GTAQuat) = [ENTITY_OFFSET, SLOT_ENTITY_SET_ROTATION];
391}
392
393impl ComponentInterface for IObjectsComponent {
396 const UID: UID = OBJECTS_COMPONENT_UID;
397}
398
399impl ComponentInterface for IPickupsComponent {
400 const UID: UID = PICKUPS_COMPONENT_UID;
401}
402
403impl ComponentInterface for ITextDrawsComponent {
404 const UID: UID = TEXTDRAWS_COMPONENT_UID;
405}
406
407impl ComponentInterface for IGangZonesComponent {
408 const UID: UID = GANGZONES_COMPONENT_UID;
409}
410
411impl ComponentInterface for IActorsComponent {
412 const UID: UID = ACTORS_COMPONENT_UID;
413}
414
415impl ComponentInterface for ITextLabelsComponent {
416 const UID: UID = TEXTLABELS_COMPONENT_UID;
417}
418
419impl ComponentInterface for IMenusComponent {
420 const UID: UID = MENUS_COMPONENT_UID;
421}
422
423impl ComponentInterface for IClassesComponent {
424 const UID: UID = CLASSES_COMPONENT_UID;
425}
426
427#[cfg(test)]
428mod tests {
429 use super::*;
430
431 #[test]
432 fn uids_match_the_headers() {
433 assert_eq!(OBJECTS_COMPONENT_UID, 0x59f8_415f_72da_6160);
434 assert_eq!(PICKUPS_COMPONENT_UID, 0xcf30_4faa_363d_d971);
435 }
436
437 #[test]
438 fn slots_match_what_clang_reports() {
439 #[cfg(not(target_env = "msvc"))]
440 assert_eq!([SLOT_CREATE_OBJECT, SLOT_CREATE_PICKUP], [21, 19]);
441 #[cfg(target_env = "msvc")]
442 assert_eq!([SLOT_CREATE_OBJECT, SLOT_CREATE_PICKUP], [19, 17]);
443 }
444
445 #[test]
446 fn the_other_uids_and_slots_match() {
447 assert_eq!(TEXTDRAWS_COMPONENT_UID, 0x9b5d_c2b1_d15c_992a);
448 assert_eq!(GANGZONES_COMPONENT_UID, 0xb335_1d11_ee8d_8056);
449 assert_eq!(ACTORS_COMPONENT_UID, 0xc81c_a021_eae2_ad5c);
450
451 #[cfg(not(target_env = "msvc"))]
454 assert_eq!(
455 [
456 SLOT_CREATE_TEXTDRAW,
457 SLOT_CREATE_GANGZONE,
458 SLOT_CREATE_ACTOR
459 ],
460 [19, 19, 19]
461 );
462 #[cfg(target_env = "msvc")]
463 assert_eq!(
464 [
465 SLOT_CREATE_TEXTDRAW,
466 SLOT_CREATE_GANGZONE,
467 SLOT_CREATE_ACTOR
468 ],
469 [18, 17, 17]
470 );
471 }
472
473 #[test]
474 fn the_last_batch_of_uids_and_slots_match() {
475 assert_eq!(TEXTLABELS_COMPONENT_UID, 0xa0c5_7ea8_0a00_9742);
476 assert_eq!(MENUS_COMPONENT_UID, 0x621e_219e_b97e_e0b2);
477 assert_eq!(CLASSES_COMPONENT_UID, 0x8cfb_3183_976d_a208);
478
479 #[cfg(not(target_env = "msvc"))]
483 assert_eq!(
484 [SLOT_CREATE_TEXTLABEL, SLOT_CREATE_MENU, SLOT_CREATE_CLASS],
485 [18, 19, 19]
486 );
487 #[cfg(target_env = "msvc")]
488 assert_eq!(
489 [SLOT_CREATE_TEXTLABEL, SLOT_CREATE_MENU, SLOT_CREATE_CLASS],
490 [18, 17, 17]
491 );
492
493 assert_eq!(
496 std::mem::size_of::<WeaponSlot>(),
497 8,
498 "padded to 4-byte alignment"
499 );
500 assert_eq!(MAX_WEAPON_SLOTS, 13);
501 }
502
503 #[test]
504 fn null_components_create_nothing() {
505 let zero = Vector3::ZERO;
506 assert!(unsafe { create_object(std::ptr::null_mut(), 1337, zero, zero, 0.0) }.is_null());
507 assert!(unsafe { create_pickup(std::ptr::null_mut(), 1274, 1, zero, 0, true) }.is_null());
508 }
509}