1use super::players::{ENTITY_OFFSET, SLOT_ENTITY_GET_ID};
18use super::server::ServerComponent;
19use super::types::{Colour, StringView, UID, Vector2, Vector3};
20use super::vtable::call_vtable;
21
22pub const TEXTDRAWS_COMPONENT_UID: UID = 0x9b5d_c2b1_d15c_992a;
24
25pub const GANGZONES_COMPONENT_UID: UID = 0xb335_1d11_ee8d_8056;
27
28pub const ACTORS_COMPONENT_UID: UID = 0xc81c_a021_eae2_ad5c;
30
31pub const TEXTLABELS_COMPONENT_UID: UID = 0xa0c5_7ea8_0a00_9742;
33
34pub const MENUS_COMPONENT_UID: UID = 0x621e_219e_b97e_e0b2;
36
37pub const CLASSES_COMPONENT_UID: UID = 0x8cfb_3183_976d_a208;
39
40pub const OBJECTS_COMPONENT_UID: UID = 0x59f8_415f_72da_6160;
42
43pub const PICKUPS_COMPONENT_UID: UID = 0xcf30_4faa_363d_d971;
45
46#[cfg(not(target_env = "msvc"))]
47const SLOT_CREATE_OBJECT: usize = 21;
48#[cfg(target_env = "msvc")]
49const SLOT_CREATE_OBJECT: usize = 19;
50
51#[cfg(not(target_env = "msvc"))]
52const SLOT_CREATE_PICKUP: usize = 19;
53#[cfg(target_env = "msvc")]
54const SLOT_CREATE_PICKUP: usize = 17;
55
56#[cfg(not(target_env = "msvc"))]
59const SLOT_CREATE_TEXTDRAW: usize = 19;
60#[cfg(target_env = "msvc")]
61const SLOT_CREATE_TEXTDRAW: usize = 18;
62
63#[cfg(not(target_env = "msvc"))]
64const SLOT_CREATE_GANGZONE: usize = 19;
65#[cfg(target_env = "msvc")]
66const SLOT_CREATE_GANGZONE: usize = 17;
67
68#[cfg(not(target_env = "msvc"))]
69const SLOT_CREATE_ACTOR: usize = 19;
70#[cfg(target_env = "msvc")]
71const SLOT_CREATE_ACTOR: usize = 17;
72
73#[cfg(not(target_env = "msvc"))]
77const SLOT_CREATE_TEXTLABEL: usize = 18;
78#[cfg(target_env = "msvc")]
79const SLOT_CREATE_TEXTLABEL: usize = 18;
80
81#[cfg(not(target_env = "msvc"))]
82const SLOT_CREATE_MENU: usize = 19;
83#[cfg(target_env = "msvc")]
84const SLOT_CREATE_MENU: usize = 17;
85
86#[cfg(not(target_env = "msvc"))]
87const SLOT_CREATE_CLASS: usize = 19;
88#[cfg(target_env = "msvc")]
89const SLOT_CREATE_CLASS: usize = 17;
90
91pub const MAX_WEAPON_SLOTS: usize = 13;
93
94#[repr(C)]
96#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
97pub struct WeaponSlot {
98 pub id: u8,
99 pub ammo: u32,
100}
101
102#[repr(C)]
104pub struct ITextLabelsComponent {
105 _opaque: [u8; 0],
106}
107
108#[repr(C)]
110pub struct ITextLabel {
111 _opaque: [u8; 0],
112}
113
114#[repr(C)]
116pub struct IMenusComponent {
117 _opaque: [u8; 0],
118}
119
120#[repr(C)]
122pub struct IMenu {
123 _opaque: [u8; 0],
124}
125
126#[repr(C)]
128pub struct IClassesComponent {
129 _opaque: [u8; 0],
130}
131
132#[repr(C)]
134pub struct IClass {
135 _opaque: [u8; 0],
136}
137
138#[repr(C)]
140pub struct ITextDrawsComponent {
141 _opaque: [u8; 0],
142}
143
144#[repr(C)]
146pub struct ITextDraw {
147 _opaque: [u8; 0],
148}
149
150#[repr(C)]
152pub struct IGangZonesComponent {
153 _opaque: [u8; 0],
154}
155
156#[repr(C)]
158pub struct IGangZone {
159 _opaque: [u8; 0],
160}
161
162#[repr(C)]
164pub struct IActorsComponent {
165 _opaque: [u8; 0],
166}
167
168#[repr(C)]
170pub struct IActor {
171 _opaque: [u8; 0],
172}
173
174#[repr(C)]
176#[derive(Debug, Clone, Copy, PartialEq)]
177pub struct GangZonePos {
178 pub min: Vector2,
179 pub max: Vector2,
180}
181
182#[repr(C)]
184pub struct IObjectsComponent {
185 _opaque: [u8; 0],
186}
187
188#[repr(C)]
190pub struct IPickupsComponent {
191 _opaque: [u8; 0],
192}
193
194#[repr(C)]
196pub struct IPickup {
197 _opaque: [u8; 0],
198}
199
200pub type PickupType = i32;
204
205#[must_use]
210pub unsafe fn as_objects_component(component: *mut ServerComponent) -> *mut IObjectsComponent {
211 component.cast::<IObjectsComponent>()
212}
213
214#[must_use]
219pub unsafe fn as_pickups_component(component: *mut ServerComponent) -> *mut IPickupsComponent {
220 component.cast::<IPickupsComponent>()
221}
222
223#[must_use]
231pub unsafe fn create_object(
232 component: *mut IObjectsComponent,
233 model: i32,
234 position: Vector3,
235 rotation: Vector3,
236 draw_distance: f32,
237) -> *mut super::players::IObject {
238 call_vtable!(
239 component.cast::<u8>(),
240 0,
241 SLOT_CREATE_OBJECT,
242 (i32, Vector3, Vector3, f32) -> *mut super::players::IObject,
243 (model, position, rotation, draw_distance),
244 std::ptr::null_mut()
245 )
246}
247
248#[must_use]
253pub unsafe fn create_pickup(
254 component: *mut IPickupsComponent,
255 model: i32,
256 pickup_type: PickupType,
257 position: Vector3,
258 virtual_world: u32,
259 is_static: bool,
260) -> *mut IPickup {
261 call_vtable!(
262 component.cast::<u8>(),
263 0,
264 SLOT_CREATE_PICKUP,
265 (i32, PickupType, Vector3, u32, bool) -> *mut IPickup,
266 (model, pickup_type, position, virtual_world, is_static),
267 std::ptr::null_mut()
268 )
269}
270
271#[must_use]
276pub unsafe fn as_textdraws_component(component: *mut ServerComponent) -> *mut ITextDrawsComponent {
277 component.cast::<ITextDrawsComponent>()
278}
279
280#[must_use]
285pub unsafe fn as_gangzones_component(component: *mut ServerComponent) -> *mut IGangZonesComponent {
286 component.cast::<IGangZonesComponent>()
287}
288
289#[must_use]
294pub unsafe fn as_actors_component(component: *mut ServerComponent) -> *mut IActorsComponent {
295 component.cast::<IActorsComponent>()
296}
297
298#[must_use]
305pub unsafe fn create_textdraw(
306 component: *mut ITextDrawsComponent,
307 position: Vector2,
308 text: &str,
309) -> *mut ITextDraw {
310 let text = StringView {
311 data: text.as_ptr(),
312 len: text.len(),
313 };
314 call_vtable!(
315 component.cast::<u8>(),
316 0,
317 SLOT_CREATE_TEXTDRAW,
318 (Vector2, StringView) -> *mut ITextDraw,
319 (position, text),
320 std::ptr::null_mut()
321 )
322}
323
324#[must_use]
329pub unsafe fn create_gangzone(
330 component: *mut IGangZonesComponent,
331 area: GangZonePos,
332) -> *mut IGangZone {
333 call_vtable!(
334 component.cast::<u8>(),
335 0,
336 SLOT_CREATE_GANGZONE,
337 (GangZonePos) -> *mut IGangZone,
338 (area),
339 std::ptr::null_mut()
340 )
341}
342
343#[must_use]
348pub unsafe fn create_actor(
349 component: *mut IActorsComponent,
350 skin: i32,
351 position: Vector3,
352 angle: f32,
353) -> *mut IActor {
354 call_vtable!(
355 component.cast::<u8>(),
356 0,
357 SLOT_CREATE_ACTOR,
358 (i32, Vector3, f32) -> *mut IActor,
359 (skin, position, angle),
360 std::ptr::null_mut()
361 )
362}
363
364#[must_use]
369pub unsafe fn as_textlabels_component(
370 component: *mut ServerComponent,
371) -> *mut ITextLabelsComponent {
372 component.cast::<ITextLabelsComponent>()
373}
374
375#[must_use]
380pub unsafe fn as_menus_component(component: *mut ServerComponent) -> *mut IMenusComponent {
381 component.cast::<IMenusComponent>()
382}
383
384#[must_use]
389pub unsafe fn as_classes_component(component: *mut ServerComponent) -> *mut IClassesComponent {
390 component.cast::<IClassesComponent>()
391}
392
393#[must_use]
401#[allow(clippy::too_many_arguments)]
402pub unsafe fn create_textlabel(
403 component: *mut ITextLabelsComponent,
404 text: &str,
405 colour: Colour,
406 position: Vector3,
407 draw_distance: f32,
408 virtual_world: i32,
409 line_of_sight: bool,
410) -> *mut ITextLabel {
411 let text = StringView {
412 data: text.as_ptr(),
413 len: text.len(),
414 };
415 call_vtable!(
416 component.cast::<u8>(),
417 0,
418 SLOT_CREATE_TEXTLABEL,
419 (StringView, Colour, Vector3, f32, i32, bool) -> *mut ITextLabel,
420 (text, colour, position, draw_distance, virtual_world, line_of_sight),
421 std::ptr::null_mut()
422 )
423}
424
425#[must_use]
430pub unsafe fn create_menu(
431 component: *mut IMenusComponent,
432 title: &str,
433 position: Vector2,
434 columns: u8,
435 column1_width: f32,
436 column2_width: f32,
437) -> *mut IMenu {
438 let title = StringView {
439 data: title.as_ptr(),
440 len: title.len(),
441 };
442 call_vtable!(
443 component.cast::<u8>(),
444 0,
445 SLOT_CREATE_MENU,
446 (StringView, Vector2, u8, f32, f32) -> *mut IMenu,
447 (title, position, columns, column1_width, column2_width),
448 std::ptr::null_mut()
449 )
450}
451
452#[must_use]
461pub unsafe fn create_class(
462 component: *mut IClassesComponent,
463 skin: i32,
464 team: i32,
465 spawn: Vector3,
466 angle: f32,
467 weapons: &[WeaponSlot; MAX_WEAPON_SLOTS],
468) -> *mut IClass {
469 call_vtable!(
470 component.cast::<u8>(),
471 0,
472 SLOT_CREATE_CLASS,
473 (i32, i32, Vector3, f32, *const WeaponSlot) -> *mut IClass,
474 (skin, team, spawn, angle, weapons.as_ptr()),
475 std::ptr::null_mut()
476 )
477}
478
479#[must_use]
486pub unsafe fn entity_id(entity: *mut u8) -> i32 {
487 call_vtable!(entity, ENTITY_OFFSET, SLOT_ENTITY_GET_ID, () -> i32, (), -1)
488}
489
490#[cfg(test)]
491mod tests {
492 use super::*;
493
494 #[test]
495 fn uids_match_the_headers() {
496 assert_eq!(OBJECTS_COMPONENT_UID, 0x59f8_415f_72da_6160);
497 assert_eq!(PICKUPS_COMPONENT_UID, 0xcf30_4faa_363d_d971);
498 }
499
500 #[test]
501 fn slots_match_what_clang_reports() {
502 #[cfg(not(target_env = "msvc"))]
503 assert_eq!([SLOT_CREATE_OBJECT, SLOT_CREATE_PICKUP], [21, 19]);
504 #[cfg(target_env = "msvc")]
505 assert_eq!([SLOT_CREATE_OBJECT, SLOT_CREATE_PICKUP], [19, 17]);
506 }
507
508 #[test]
509 fn the_other_uids_and_slots_match() {
510 assert_eq!(TEXTDRAWS_COMPONENT_UID, 0x9b5d_c2b1_d15c_992a);
511 assert_eq!(GANGZONES_COMPONENT_UID, 0xb335_1d11_ee8d_8056);
512 assert_eq!(ACTORS_COMPONENT_UID, 0xc81c_a021_eae2_ad5c);
513
514 #[cfg(not(target_env = "msvc"))]
517 assert_eq!(
518 [
519 SLOT_CREATE_TEXTDRAW,
520 SLOT_CREATE_GANGZONE,
521 SLOT_CREATE_ACTOR
522 ],
523 [19, 19, 19]
524 );
525 #[cfg(target_env = "msvc")]
526 assert_eq!(
527 [
528 SLOT_CREATE_TEXTDRAW,
529 SLOT_CREATE_GANGZONE,
530 SLOT_CREATE_ACTOR
531 ],
532 [18, 17, 17]
533 );
534 }
535
536 #[test]
537 fn the_last_batch_of_uids_and_slots_match() {
538 assert_eq!(TEXTLABELS_COMPONENT_UID, 0xa0c5_7ea8_0a00_9742);
539 assert_eq!(MENUS_COMPONENT_UID, 0x621e_219e_b97e_e0b2);
540 assert_eq!(CLASSES_COMPONENT_UID, 0x8cfb_3183_976d_a208);
541
542 #[cfg(not(target_env = "msvc"))]
546 assert_eq!(
547 [SLOT_CREATE_TEXTLABEL, SLOT_CREATE_MENU, SLOT_CREATE_CLASS],
548 [18, 19, 19]
549 );
550 #[cfg(target_env = "msvc")]
551 assert_eq!(
552 [SLOT_CREATE_TEXTLABEL, SLOT_CREATE_MENU, SLOT_CREATE_CLASS],
553 [18, 17, 17]
554 );
555
556 assert_eq!(
559 std::mem::size_of::<WeaponSlot>(),
560 8,
561 "padded to 4-byte alignment"
562 );
563 assert_eq!(MAX_WEAPON_SLOTS, 13);
564 }
565
566 #[test]
567 fn null_components_create_nothing() {
568 let zero = Vector3 {
569 x: 0.0,
570 y: 0.0,
571 z: 0.0,
572 };
573 assert!(unsafe { create_object(std::ptr::null_mut(), 1337, zero, zero, 0.0) }.is_null());
574 assert!(unsafe { create_pickup(std::ptr::null_mut(), 1274, 1, zero, 0, true) }.is_null());
575 }
576}