1use rakata_core::ResRef;
33use rakata_formats::{
34 gff_schema::{AbsentDefault, FieldLife, FieldSchema, GffType},
35 GffStruct, GffValue,
36};
37
38use crate::are::AreError;
39use crate::gff_helpers::{get_bool, get_f32, get_i32, get_resref, get_u32, get_u8, upsert_field};
40
41#[derive(Debug, Clone, PartialEq)]
43pub struct AreMiniGame {
44 pub mini_game_type: u32,
46 pub movement_per_sec: f32,
48 pub lateral_accel: f32,
50 pub bump_plane: u32,
52 pub do_bumping: bool,
54 pub use_inertia: bool,
56 pub dof: u32,
58 pub music: ResRef,
60 pub far_clip: f32,
62 pub near_clip: f32,
64 pub camera_view_angle: f32,
66 pub player: Option<AreMiniGamePlayer>,
68 pub mouse: Option<AreMiniGameMouse>,
86 pub enemies: Vec<AreMiniGameEnemy>,
88 pub obstacles: Vec<AreMiniGameObstacle>,
90}
91
92impl Default for AreMiniGame {
93 fn default() -> Self {
94 Self {
95 mini_game_type: 0,
96 movement_per_sec: 0.0,
97 lateral_accel: 60.0,
98 bump_plane: 0,
99 do_bumping: false,
100 use_inertia: false,
101 dof: 0,
102 music: ResRef::blank(),
103 far_clip: 100.0,
104 near_clip: 0.1,
105 camera_view_angle: 65.0,
106 player: None,
107 mouse: None,
108 enemies: Vec::new(),
109 obstacles: Vec::new(),
110 }
111 }
112}
113
114impl AreMiniGame {
115 pub(crate) fn from_struct(structure: &GffStruct) -> Result<Self, AreError> {
116 let player = match structure.field("Player") {
117 Some(GffValue::Struct(s)) => Some(AreMiniGamePlayer::from_struct(s)?),
118 Some(_) => {
119 return Err(AreError::TypeMismatch {
120 field: "MiniGame.Player",
121 expected: "Struct",
122 });
123 }
124 None => None,
125 };
126
127 let mouse = match structure.field("Mouse") {
133 Some(GffValue::Struct(s)) => Some(AreMiniGameMouse::from_struct(s)),
134 Some(_) => {
135 return Err(AreError::TypeMismatch {
136 field: "MiniGame.Mouse",
137 expected: "Struct",
138 });
139 }
140 None => None,
141 };
142
143 let enemies = match structure.field("Enemies") {
144 Some(GffValue::List(items)) => items
145 .iter()
146 .map(AreMiniGameEnemy::from_struct)
147 .collect::<Result<Vec<_>, _>>()?,
148 Some(_) => {
149 return Err(AreError::TypeMismatch {
150 field: "MiniGame.Enemies",
151 expected: "List",
152 });
153 }
154 None => Vec::new(),
155 };
156
157 let obstacles = match structure.field("Obstacles") {
158 Some(GffValue::List(items)) => items
159 .iter()
160 .map(AreMiniGameObstacle::from_struct)
161 .collect::<Result<Vec<_>, _>>()?,
162 Some(_) => {
163 return Err(AreError::TypeMismatch {
164 field: "MiniGame.Obstacles",
165 expected: "List",
166 });
167 }
168 None => Vec::new(),
169 };
170
171 Ok(Self {
172 mini_game_type: get_u32(structure, "Type").unwrap_or(0),
173 movement_per_sec: get_f32(structure, "MovementPerSec").unwrap_or(0.0),
174 lateral_accel: get_f32(structure, "LateralAccel").unwrap_or(60.0),
175 bump_plane: get_u32(structure, "Bump_Plane").unwrap_or(0),
176 do_bumping: get_bool(structure, "DoBumping").unwrap_or(false),
177 use_inertia: get_bool(structure, "UseInertia").unwrap_or(false),
178 dof: get_u32(structure, "DOF").unwrap_or(0),
179 music: get_resref(structure, "Music").unwrap_or_default(),
180 far_clip: get_f32(structure, "Far_Clip").unwrap_or(100.0),
181 near_clip: get_f32(structure, "Near_Clip").unwrap_or(0.1),
182 camera_view_angle: get_f32(structure, "CameraViewAngle").unwrap_or(65.0),
183 player,
184 mouse,
185 enemies,
186 obstacles,
187 })
188 }
189
190 pub(crate) fn to_struct(&self) -> GffStruct {
191 let mut s = GffStruct::new(0);
192 upsert_field(&mut s, "Type", GffValue::UInt32(self.mini_game_type));
193 upsert_field(
194 &mut s,
195 "MovementPerSec",
196 GffValue::Single(self.movement_per_sec),
197 );
198 upsert_field(&mut s, "LateralAccel", GffValue::Single(self.lateral_accel));
199 upsert_field(&mut s, "Bump_Plane", GffValue::UInt32(self.bump_plane));
200 upsert_field(
201 &mut s,
202 "DoBumping",
203 GffValue::UInt8(u8::from(self.do_bumping)),
204 );
205 upsert_field(
206 &mut s,
207 "UseInertia",
208 GffValue::UInt8(u8::from(self.use_inertia)),
209 );
210 upsert_field(&mut s, "DOF", GffValue::UInt32(self.dof));
211 upsert_field(&mut s, "Music", GffValue::ResRef(self.music));
212 upsert_field(&mut s, "Far_Clip", GffValue::Single(self.far_clip));
213 upsert_field(&mut s, "Near_Clip", GffValue::Single(self.near_clip));
214 upsert_field(
215 &mut s,
216 "CameraViewAngle",
217 GffValue::Single(self.camera_view_angle),
218 );
219 if let Some(player) = &self.player {
220 upsert_field(
221 &mut s,
222 "Player",
223 GffValue::Struct(Box::new(player.to_struct())),
224 );
225 }
226 if let Some(mouse) = &self.mouse {
227 upsert_field(
228 &mut s,
229 "Mouse",
230 GffValue::Struct(Box::new(mouse.to_struct())),
231 );
232 }
233 let enemy_structs: Vec<GffStruct> = self
234 .enemies
235 .iter()
236 .map(AreMiniGameEnemy::to_struct)
237 .collect();
238 upsert_field(&mut s, "Enemies", GffValue::List(enemy_structs));
239 let obstacle_structs: Vec<GffStruct> = self
240 .obstacles
241 .iter()
242 .map(AreMiniGameObstacle::to_struct)
243 .collect();
244 upsert_field(&mut s, "Obstacles", GffValue::List(obstacle_structs));
245 s
246 }
247}
248
249#[derive(Debug, Clone, PartialEq, Default)]
265pub struct AreMiniGameObjectScripts {
266 pub on_create: ResRef,
268 pub on_heartbeat: ResRef,
270 pub on_anim_event: ResRef,
272 pub on_hit_bullet: ResRef,
274 pub on_hit_follower: ResRef,
276}
277
278impl AreMiniGameObjectScripts {
279 fn from_struct(structure: &GffStruct) -> Self {
280 Self {
281 on_create: get_resref(structure, "OnCreate").unwrap_or_default(),
282 on_heartbeat: get_resref(structure, "OnHeartbeat").unwrap_or_default(),
283 on_anim_event: get_resref(structure, "OnAnimEvent").unwrap_or_default(),
284 on_hit_bullet: get_resref(structure, "OnHitBullet").unwrap_or_default(),
285 on_hit_follower: get_resref(structure, "OnHitFollower").unwrap_or_default(),
286 }
287 }
288
289 fn write_into(&self, s: &mut GffStruct) {
290 upsert_field(s, "OnCreate", GffValue::ResRef(self.on_create));
291 upsert_field(s, "OnHeartbeat", GffValue::ResRef(self.on_heartbeat));
292 upsert_field(s, "OnAnimEvent", GffValue::ResRef(self.on_anim_event));
293 upsert_field(s, "OnHitBullet", GffValue::ResRef(self.on_hit_bullet));
294 upsert_field(s, "OnHitFollower", GffValue::ResRef(self.on_hit_follower));
295 }
296
297 fn to_struct(&self) -> GffStruct {
298 let mut s = GffStruct::new(0);
299 self.write_into(&mut s);
300 s
301 }
302}
303
304#[derive(Debug, Clone, PartialEq, Default)]
310pub struct AreMiniGameVehicleScripts {
311 pub object: AreMiniGameObjectScripts,
313 pub on_damage: ResRef,
315 pub on_death: ResRef,
317 pub on_fire: ResRef,
319 pub on_hit_obstacle: ResRef,
321 pub on_track_loop: ResRef,
323}
324
325impl AreMiniGameVehicleScripts {
326 fn from_struct(structure: &GffStruct) -> Self {
327 Self {
328 object: AreMiniGameObjectScripts::from_struct(structure),
329 on_damage: get_resref(structure, "OnDamage").unwrap_or_default(),
330 on_death: get_resref(structure, "OnDeath").unwrap_or_default(),
331 on_fire: get_resref(structure, "OnFire").unwrap_or_default(),
332 on_hit_obstacle: get_resref(structure, "OnHitObstacle").unwrap_or_default(),
333 on_track_loop: get_resref(structure, "OnTrackLoop").unwrap_or_default(),
334 }
335 }
336
337 fn to_struct(&self) -> GffStruct {
338 let mut s = GffStruct::new(0);
339 self.object.write_into(&mut s);
340 upsert_field(&mut s, "OnDamage", GffValue::ResRef(self.on_damage));
341 upsert_field(&mut s, "OnDeath", GffValue::ResRef(self.on_death));
342 upsert_field(&mut s, "OnFire", GffValue::ResRef(self.on_fire));
343 upsert_field(
344 &mut s,
345 "OnHitObstacle",
346 GffValue::ResRef(self.on_hit_obstacle),
347 );
348 upsert_field(&mut s, "OnTrackLoop", GffValue::ResRef(self.on_track_loop));
349 s
350 }
351}
352
353#[derive(Debug, Clone, PartialEq, Default)]
360pub struct AreMiniGameSounds {
361 pub engine: ResRef,
363 pub death: ResRef,
365}
366
367impl AreMiniGameSounds {
368 fn from_struct(structure: &GffStruct) -> Self {
369 Self {
370 engine: get_resref(structure, "Engine").unwrap_or_default(),
371 death: get_resref(structure, "Death").unwrap_or_default(),
372 }
373 }
374
375 fn to_struct(&self) -> GffStruct {
376 let mut s = GffStruct::new(0);
377 upsert_field(&mut s, "Engine", GffValue::ResRef(self.engine));
378 upsert_field(&mut s, "Death", GffValue::ResRef(self.death));
379 s
380 }
381}
382
383pub const BANK_ID_NONE: u32 = 0xFFFF_FFFF;
392
393#[derive(Debug, Clone, PartialEq, Default)]
408pub struct AreMiniGameBullet {
409 pub damage: u32,
411 pub lifespan: f32,
413 pub rate_of_fire: f32,
415 pub speed: f32,
417 pub target_type: u32,
419 pub bullet_model: ResRef,
421 pub collision_sound: ResRef,
423}
424
425impl AreMiniGameBullet {
426 fn from_struct(structure: &GffStruct) -> Self {
427 Self {
428 damage: get_u32(structure, "Damage").unwrap_or(0),
429 lifespan: get_f32(structure, "Lifespan").unwrap_or(0.0),
430 rate_of_fire: get_f32(structure, "Rate_Of_Fire").unwrap_or(0.0),
431 speed: get_f32(structure, "Speed").unwrap_or(0.0),
432 target_type: get_u32(structure, "Target_Type").unwrap_or(0),
433 bullet_model: get_resref(structure, "Bullet_Model").unwrap_or_default(),
434 collision_sound: get_resref(structure, "Collision_Sound").unwrap_or_default(),
435 }
436 }
437
438 fn to_struct(&self) -> GffStruct {
439 let mut s = GffStruct::new(0);
440 upsert_field(&mut s, "Damage", GffValue::UInt32(self.damage));
441 upsert_field(&mut s, "Lifespan", GffValue::Single(self.lifespan));
442 upsert_field(&mut s, "Rate_Of_Fire", GffValue::Single(self.rate_of_fire));
443 upsert_field(&mut s, "Speed", GffValue::Single(self.speed));
444 upsert_field(&mut s, "Target_Type", GffValue::UInt32(self.target_type));
445 upsert_field(&mut s, "Bullet_Model", GffValue::ResRef(self.bullet_model));
446 upsert_field(
447 &mut s,
448 "Collision_Sound",
449 GffValue::ResRef(self.collision_sound),
450 );
451 s
452 }
453}
454
455#[derive(Debug, Clone, PartialEq, Default)]
462pub struct AreMiniGameTargeting {
463 pub sensing_radius: f32,
465 pub horiz_spread: f32,
467 pub vert_spread: f32,
469 pub inaccuracy: f32,
471}
472
473impl AreMiniGameTargeting {
474 const LABELS: [&'static str; 4] = [
476 "Sensing_Radius",
477 "Horiz_Spread",
478 "Vert_Spread",
479 "Inaccuracy",
480 ];
481
482 fn from_struct(structure: &GffStruct) -> Option<Self> {
491 if !Self::LABELS
492 .iter()
493 .all(|label| structure.field(label).is_some())
494 {
495 return None;
496 }
497 Some(Self {
498 sensing_radius: get_f32(structure, "Sensing_Radius").unwrap_or(0.0),
499 horiz_spread: get_f32(structure, "Horiz_Spread").unwrap_or(0.0),
500 vert_spread: get_f32(structure, "Vert_Spread").unwrap_or(0.0),
501 inaccuracy: get_f32(structure, "Inaccuracy").unwrap_or(0.0),
502 })
503 }
504
505 fn write_into(&self, s: &mut GffStruct) {
506 upsert_field(s, "Sensing_Radius", GffValue::Single(self.sensing_radius));
507 upsert_field(s, "Horiz_Spread", GffValue::Single(self.horiz_spread));
508 upsert_field(s, "Vert_Spread", GffValue::Single(self.vert_spread));
509 upsert_field(s, "Inaccuracy", GffValue::Single(self.inaccuracy));
510 }
511}
512
513#[derive(Debug, Clone, PartialEq)]
515pub struct AreMiniGameGunBank {
516 pub bank_id: u32,
520 pub gun_model: ResRef,
525 pub bullet: Option<AreMiniGameBullet>,
533 pub fire_sound: ResRef,
537 pub targeting: Option<AreMiniGameTargeting>,
540}
541
542impl Default for AreMiniGameGunBank {
543 fn default() -> Self {
544 Self {
545 bank_id: BANK_ID_NONE,
546 gun_model: ResRef::blank(),
547 bullet: None,
548 fire_sound: ResRef::blank(),
549 targeting: None,
550 }
551 }
552}
553
554impl AreMiniGameGunBank {
555 fn from_struct(structure: &GffStruct, bullet_field: &'static str) -> Result<Self, AreError> {
556 let bullet = match structure.field("Bullet") {
557 Some(GffValue::Struct(s)) => Some(AreMiniGameBullet::from_struct(s)),
558 Some(_) => {
559 return Err(AreError::TypeMismatch {
560 field: bullet_field,
561 expected: "Struct",
562 });
563 }
564 None => None,
565 };
566
567 Ok(Self {
568 bank_id: get_u32(structure, "BankID").unwrap_or(BANK_ID_NONE),
569 gun_model: get_resref(structure, "Gun_Model").unwrap_or_default(),
570 bullet,
571 fire_sound: get_resref(structure, "Fire_Sound").unwrap_or_default(),
572 targeting: AreMiniGameTargeting::from_struct(structure),
573 })
574 }
575
576 fn to_struct(&self) -> GffStruct {
577 let mut s = GffStruct::new(0);
578 upsert_field(&mut s, "BankID", GffValue::UInt32(self.bank_id));
579 upsert_field(&mut s, "Gun_Model", GffValue::ResRef(self.gun_model));
580 if let Some(bullet) = &self.bullet {
581 upsert_field(
582 &mut s,
583 "Bullet",
584 GffValue::Struct(Box::new(bullet.to_struct())),
585 );
586 }
587 upsert_field(&mut s, "Fire_Sound", GffValue::ResRef(self.fire_sound));
588 if let Some(targeting) = &self.targeting {
589 targeting.write_into(&mut s);
590 }
591 s
592 }
593}
594
595struct VehiclePaths {
601 models: &'static str,
602 scripts: &'static str,
603 sounds: &'static str,
604 gun_banks: &'static str,
605 bullet: &'static str,
606}
607
608const PLAYER_PATHS: VehiclePaths = VehiclePaths {
609 models: "MiniGame.Player.Models",
610 scripts: "MiniGame.Player.Scripts",
611 sounds: "MiniGame.Player.Sounds",
612 gun_banks: "MiniGame.Player.Gun_Banks",
613 bullet: "MiniGame.Player.Gun_Banks[].Bullet",
614};
615
616const ENEMY_PATHS: VehiclePaths = VehiclePaths {
617 models: "MiniGame.Enemies[].Models",
618 scripts: "MiniGame.Enemies[].Scripts",
619 sounds: "MiniGame.Enemies[].Sounds",
620 gun_banks: "MiniGame.Enemies[].Gun_Banks",
621 bullet: "MiniGame.Enemies[].Gun_Banks[].Bullet",
622};
623
624#[derive(Debug, Clone, PartialEq)]
642pub struct AreMiniGameVehicle {
643 pub models: Vec<AreMiniGameModel>,
645 pub track: ResRef,
647 pub hit_points: u32,
653 pub max_hps: u32,
655 pub sphere_radius: f32,
661 pub invince_period: f32,
663 pub bump_damage: i32,
665 pub num_loops: i32,
671 pub scripts: AreMiniGameVehicleScripts,
682 pub sounds: AreMiniGameSounds,
685 pub gun_banks: Vec<AreMiniGameGunBank>,
687}
688
689impl Default for AreMiniGameVehicle {
690 fn default() -> Self {
691 Self {
692 models: Vec::new(),
693 track: ResRef::blank(),
694 hit_points: 0,
695 max_hps: 0,
696 sphere_radius: -1.0,
697 invince_period: 0.0,
698 bump_damage: 0,
699 num_loops: -10,
700 scripts: AreMiniGameVehicleScripts::default(),
701 sounds: AreMiniGameSounds::default(),
702 gun_banks: Vec::new(),
703 }
704 }
705}
706
707impl AreMiniGameVehicle {
708 fn from_struct(structure: &GffStruct, paths: &VehiclePaths) -> Result<Self, AreError> {
710 let models = match structure.field("Models") {
711 Some(GffValue::List(items)) => items
712 .iter()
713 .map(AreMiniGameModel::from_struct)
714 .collect::<Vec<_>>(),
715 Some(_) => {
716 return Err(AreError::TypeMismatch {
717 field: paths.models,
718 expected: "List",
719 });
720 }
721 None => Vec::new(),
722 };
723
724 let scripts = match structure.field("Scripts") {
725 Some(GffValue::Struct(s)) => AreMiniGameVehicleScripts::from_struct(s),
726 Some(_) => {
727 return Err(AreError::TypeMismatch {
728 field: paths.scripts,
729 expected: "Struct",
730 });
731 }
732 None => AreMiniGameVehicleScripts::default(),
733 };
734
735 let sounds = match structure.field("Sounds") {
736 Some(GffValue::Struct(s)) => AreMiniGameSounds::from_struct(s),
737 Some(_) => {
738 return Err(AreError::TypeMismatch {
739 field: paths.sounds,
740 expected: "Struct",
741 });
742 }
743 None => AreMiniGameSounds::default(),
744 };
745
746 let gun_banks = match structure.field("Gun_Banks") {
747 Some(GffValue::List(items)) => items
748 .iter()
749 .map(|item| AreMiniGameGunBank::from_struct(item, paths.bullet))
750 .collect::<Result<Vec<_>, _>>()?,
751 Some(_) => {
752 return Err(AreError::TypeMismatch {
753 field: paths.gun_banks,
754 expected: "List",
755 });
756 }
757 None => Vec::new(),
758 };
759
760 Ok(Self {
761 models,
762 track: get_resref(structure, "Track").unwrap_or_default(),
763 hit_points: get_u32(structure, "Hit_Points").unwrap_or(0),
764 max_hps: get_u32(structure, "Max_HPs").unwrap_or(0),
765 sphere_radius: get_f32(structure, "Sphere_Radius").unwrap_or(-1.0),
766 invince_period: get_f32(structure, "Invince_Period").unwrap_or(0.0),
767 bump_damage: get_i32(structure, "Bump_Damage").unwrap_or(0),
768 num_loops: get_i32(structure, "Num_Loops").unwrap_or(-10),
769 scripts,
770 sounds,
771 gun_banks,
772 })
773 }
774
775 fn write_into(&self, s: &mut GffStruct) {
778 let model_structs: Vec<GffStruct> = self
779 .models
780 .iter()
781 .map(AreMiniGameModel::to_struct)
782 .collect();
783 upsert_field(s, "Models", GffValue::List(model_structs));
784 upsert_field(s, "Track", GffValue::ResRef(self.track));
785 upsert_field(s, "Hit_Points", GffValue::UInt32(self.hit_points));
786 upsert_field(s, "Max_HPs", GffValue::UInt32(self.max_hps));
787 upsert_field(s, "Sphere_Radius", GffValue::Single(self.sphere_radius));
788 upsert_field(s, "Invince_Period", GffValue::Single(self.invince_period));
789 upsert_field(s, "Bump_Damage", GffValue::Int32(self.bump_damage));
790 upsert_field(s, "Num_Loops", GffValue::Int32(self.num_loops));
791 upsert_field(
792 s,
793 "Scripts",
794 GffValue::Struct(Box::new(self.scripts.to_struct())),
795 );
796 upsert_field(
797 s,
798 "Sounds",
799 GffValue::Struct(Box::new(self.sounds.to_struct())),
800 );
801 let bank_structs: Vec<GffStruct> = self
802 .gun_banks
803 .iter()
804 .map(AreMiniGameGunBank::to_struct)
805 .collect();
806 upsert_field(s, "Gun_Banks", GffValue::List(bank_structs));
807 }
808}
809
810#[derive(Debug, Clone, PartialEq)]
812pub struct AreMiniGamePlayer {
813 pub vehicle: AreMiniGameVehicle,
815 pub camera: ResRef,
817 pub camera_rotate: bool,
819 pub minimum_speed: f32,
824 pub maximum_speed: f32,
829 pub accel_secs: f32,
838 pub tunnel_x_pos: f32,
840 pub tunnel_x_neg: f32,
842 pub tunnel_y_pos: f32,
844 pub tunnel_y_neg: f32,
846 pub tunnel_z_pos: f32,
848 pub tunnel_z_neg: f32,
850 pub tunnel_infinite: [f32; 3],
854 pub start_offset_x: f32,
860 pub start_offset_y: f32,
862 pub start_offset_z: f32,
864 pub target_offset_x: f32,
869 pub target_offset_y: f32,
871 pub target_offset_z: f32,
873}
874
875impl Default for AreMiniGamePlayer {
876 fn default() -> Self {
877 Self {
878 vehicle: AreMiniGameVehicle::default(),
879 camera: ResRef::blank(),
880 camera_rotate: false,
881 minimum_speed: -1.0,
882 maximum_speed: 100.0,
883 accel_secs: -1.0,
884 tunnel_x_pos: 0.0,
885 tunnel_x_neg: 0.0,
886 tunnel_y_pos: 0.0,
887 tunnel_y_neg: 0.0,
888 tunnel_z_pos: 0.0,
889 tunnel_z_neg: 0.0,
890 tunnel_infinite: [0.0; 3],
891 start_offset_x: 0.0,
892 start_offset_y: 0.0,
893 start_offset_z: 0.0,
894 target_offset_x: 0.0,
895 target_offset_y: 0.0,
896 target_offset_z: 0.0,
897 }
898 }
899}
900
901impl AreMiniGamePlayer {
902 fn from_struct(structure: &GffStruct) -> Result<Self, AreError> {
903 Ok(Self {
904 vehicle: AreMiniGameVehicle::from_struct(structure, &PLAYER_PATHS)?,
905 camera: get_resref(structure, "Camera").unwrap_or_default(),
906 camera_rotate: get_bool(structure, "CameraRotate").unwrap_or(false),
907 minimum_speed: get_f32(structure, "Minimum_Speed").unwrap_or(-1.0),
908 maximum_speed: get_f32(structure, "Maximum_Speed").unwrap_or(100.0),
909 accel_secs: get_f32(structure, "Accel_Secs").unwrap_or(-1.0),
910 tunnel_x_pos: get_f32(structure, "TunnelXPos").unwrap_or(0.0),
911 tunnel_x_neg: get_f32(structure, "TunnelXNeg").unwrap_or(0.0),
912 tunnel_y_pos: get_f32(structure, "TunnelYPos").unwrap_or(0.0),
913 tunnel_y_neg: get_f32(structure, "TunnelYNeg").unwrap_or(0.0),
914 tunnel_z_pos: get_f32(structure, "TunnelZPos").unwrap_or(0.0),
915 tunnel_z_neg: get_f32(structure, "TunnelZNeg").unwrap_or(0.0),
916 tunnel_infinite: match structure.field("TunnelInfinite") {
917 Some(GffValue::Vector3(v)) => *v,
918 _ => [0.0; 3],
919 },
920 start_offset_x: get_f32(structure, "Start_Offset_X").unwrap_or(0.0),
921 start_offset_y: get_f32(structure, "Start_Offset_Y").unwrap_or(0.0),
922 start_offset_z: get_f32(structure, "Start_Offset_Z").unwrap_or(0.0),
923 target_offset_x: get_f32(structure, "Target_Offset_X").unwrap_or(0.0),
924 target_offset_y: get_f32(structure, "Target_Offset_Y").unwrap_or(0.0),
925 target_offset_z: get_f32(structure, "Target_Offset_Z").unwrap_or(0.0),
926 })
927 }
928
929 fn to_struct(&self) -> GffStruct {
930 let mut s = GffStruct::new(0);
931 self.vehicle.write_into(&mut s);
932 upsert_field(&mut s, "Camera", GffValue::ResRef(self.camera));
933 upsert_field(
934 &mut s,
935 "CameraRotate",
936 GffValue::UInt8(u8::from(self.camera_rotate)),
937 );
938 upsert_field(
939 &mut s,
940 "Minimum_Speed",
941 GffValue::Single(self.minimum_speed),
942 );
943 upsert_field(
944 &mut s,
945 "Maximum_Speed",
946 GffValue::Single(self.maximum_speed),
947 );
948 upsert_field(&mut s, "Accel_Secs", GffValue::Single(self.accel_secs));
949 upsert_field(&mut s, "TunnelXPos", GffValue::Single(self.tunnel_x_pos));
950 upsert_field(&mut s, "TunnelXNeg", GffValue::Single(self.tunnel_x_neg));
951 upsert_field(&mut s, "TunnelYPos", GffValue::Single(self.tunnel_y_pos));
952 upsert_field(&mut s, "TunnelYNeg", GffValue::Single(self.tunnel_y_neg));
953 upsert_field(&mut s, "TunnelZPos", GffValue::Single(self.tunnel_z_pos));
954 upsert_field(&mut s, "TunnelZNeg", GffValue::Single(self.tunnel_z_neg));
955 upsert_field(
956 &mut s,
957 "TunnelInfinite",
958 GffValue::Vector3(self.tunnel_infinite),
959 );
960 upsert_field(
961 &mut s,
962 "Start_Offset_X",
963 GffValue::Single(self.start_offset_x),
964 );
965 upsert_field(
966 &mut s,
967 "Start_Offset_Y",
968 GffValue::Single(self.start_offset_y),
969 );
970 upsert_field(
971 &mut s,
972 "Start_Offset_Z",
973 GffValue::Single(self.start_offset_z),
974 );
975 upsert_field(
976 &mut s,
977 "Target_Offset_X",
978 GffValue::Single(self.target_offset_x),
979 );
980 upsert_field(
981 &mut s,
982 "Target_Offset_Y",
983 GffValue::Single(self.target_offset_y),
984 );
985 upsert_field(
986 &mut s,
987 "Target_Offset_Z",
988 GffValue::Single(self.target_offset_z),
989 );
990 s
991 }
992}
993
994#[derive(Debug, Clone, PartialEq)]
996pub struct AreMiniGameModel {
997 pub model: ResRef,
999 pub rotating_model: bool,
1001}
1002
1003impl Default for AreMiniGameModel {
1004 fn default() -> Self {
1005 Self {
1006 model: ResRef::blank(),
1007 rotating_model: true,
1008 }
1009 }
1010}
1011
1012impl AreMiniGameModel {
1013 fn from_struct(structure: &GffStruct) -> Self {
1014 Self {
1015 model: get_resref(structure, "Model").unwrap_or_default(),
1016 rotating_model: get_bool(structure, "RotatingModel").unwrap_or(true),
1017 }
1018 }
1019
1020 fn to_struct(&self) -> GffStruct {
1021 let mut s = GffStruct::new(0);
1022 upsert_field(&mut s, "Model", GffValue::ResRef(self.model));
1023 upsert_field(
1024 &mut s,
1025 "RotatingModel",
1026 GffValue::UInt8(u8::from(self.rotating_model)),
1027 );
1028 s
1029 }
1030}
1031
1032#[derive(Debug, Clone, PartialEq, Default)]
1034pub struct AreMiniGameMouse {
1035 pub axis_x: u32,
1037 pub axis_y: u32,
1039 pub flip_axis_x: bool,
1041 pub flip_axis_y: bool,
1043}
1044
1045impl AreMiniGameMouse {
1046 fn from_struct(structure: &GffStruct) -> Self {
1047 Self {
1048 axis_x: get_u32(structure, "AxisX").unwrap_or(0),
1049 axis_y: get_u32(structure, "AxisY").unwrap_or(0),
1050 flip_axis_x: get_bool(structure, "FlipAxisX").unwrap_or(false),
1051 flip_axis_y: get_bool(structure, "FlipAxisY").unwrap_or(false),
1052 }
1053 }
1054
1055 fn to_struct(&self) -> GffStruct {
1056 let mut s = GffStruct::new(0);
1057 upsert_field(&mut s, "AxisX", GffValue::UInt32(self.axis_x));
1058 upsert_field(&mut s, "AxisY", GffValue::UInt32(self.axis_y));
1059 upsert_field(
1060 &mut s,
1061 "FlipAxisX",
1062 GffValue::UInt8(u8::from(self.flip_axis_x)),
1063 );
1064 upsert_field(
1065 &mut s,
1066 "FlipAxisY",
1067 GffValue::UInt8(u8::from(self.flip_axis_y)),
1068 );
1069 s
1070 }
1071}
1072
1073#[derive(Debug, Clone, PartialEq, Default)]
1075pub struct AreMiniGameEnemy {
1076 pub vehicle: AreMiniGameVehicle,
1078 pub trigger: u8,
1107}
1108
1109impl AreMiniGameEnemy {
1110 fn from_struct(structure: &GffStruct) -> Result<Self, AreError> {
1111 Ok(Self {
1112 vehicle: AreMiniGameVehicle::from_struct(structure, &ENEMY_PATHS)?,
1113 trigger: get_u8(structure, "Trigger").unwrap_or(0),
1114 })
1115 }
1116
1117 fn to_struct(&self) -> GffStruct {
1118 let mut s = GffStruct::new(0);
1119 self.vehicle.write_into(&mut s);
1120 upsert_field(&mut s, "Trigger", GffValue::UInt8(self.trigger));
1121 s
1122 }
1123}
1124
1125#[derive(Debug, Clone, PartialEq, Default)]
1131pub struct AreMiniGameObstacle {
1132 pub name: ResRef,
1134 pub scripts: AreMiniGameObjectScripts,
1137}
1138
1139impl AreMiniGameObstacle {
1140 fn from_struct(structure: &GffStruct) -> Result<Self, AreError> {
1141 let scripts = match structure.field("Scripts") {
1142 Some(GffValue::Struct(s)) => AreMiniGameObjectScripts::from_struct(s),
1143 Some(_) => {
1144 return Err(AreError::TypeMismatch {
1145 field: "MiniGame.Obstacles[].Scripts",
1146 expected: "Struct",
1147 });
1148 }
1149 None => AreMiniGameObjectScripts::default(),
1150 };
1151
1152 Ok(Self {
1153 name: get_resref(structure, "Name").unwrap_or_default(),
1154 scripts,
1155 })
1156 }
1157
1158 fn to_struct(&self) -> GffStruct {
1159 let mut s = GffStruct::new(0);
1160 upsert_field(&mut s, "Name", GffValue::ResRef(self.name));
1161 upsert_field(
1162 &mut s,
1163 "Scripts",
1164 GffValue::Struct(Box::new(self.scripts.to_struct())),
1165 );
1166 s
1167 }
1168}
1169
1170static MODEL_CHILDREN: &[FieldSchema] = &[
1172 FieldSchema {
1173 label: "Model",
1174 expected_type: GffType::ResRef,
1175 life: FieldLife::Live,
1176 required: false,
1177 absent: AbsentDefault::Unverified,
1178 children: None,
1179 constraint: None,
1180 },
1181 FieldSchema {
1182 label: "RotatingModel",
1183 expected_type: GffType::UInt8,
1184 life: FieldLife::Live,
1185 required: false,
1186 absent: AbsentDefault::Unverified,
1187 children: None,
1188 constraint: None,
1189 },
1190];
1191
1192static OBJECT_SCRIPTS_CHILDREN: &[FieldSchema] = &[
1194 FieldSchema {
1195 label: "OnCreate",
1196 expected_type: GffType::ResRef,
1197 life: FieldLife::Live,
1198 required: false,
1199 absent: AbsentDefault::Unverified,
1200 children: None,
1201 constraint: None,
1202 },
1203 FieldSchema {
1204 label: "OnHeartbeat",
1205 expected_type: GffType::ResRef,
1206 life: FieldLife::Live,
1207 required: false,
1208 absent: AbsentDefault::Unverified,
1209 children: None,
1210 constraint: None,
1211 },
1212 FieldSchema {
1213 label: "OnAnimEvent",
1214 expected_type: GffType::ResRef,
1215 life: FieldLife::Live,
1216 required: false,
1217 absent: AbsentDefault::Unverified,
1218 children: None,
1219 constraint: None,
1220 },
1221 FieldSchema {
1222 label: "OnHitBullet",
1223 expected_type: GffType::ResRef,
1224 life: FieldLife::Live,
1225 required: false,
1226 absent: AbsentDefault::Unverified,
1227 children: None,
1228 constraint: None,
1229 },
1230 FieldSchema {
1231 label: "OnHitFollower",
1232 expected_type: GffType::ResRef,
1233 life: FieldLife::Live,
1234 required: false,
1235 absent: AbsentDefault::Unverified,
1236 children: None,
1237 constraint: None,
1238 },
1239];
1240
1241static VEHICLE_SCRIPTS_CHILDREN: &[FieldSchema] = &[
1243 FieldSchema {
1244 label: "OnCreate",
1245 expected_type: GffType::ResRef,
1246 life: FieldLife::Live,
1247 required: false,
1248 absent: AbsentDefault::Unverified,
1249 children: None,
1250 constraint: None,
1251 },
1252 FieldSchema {
1253 label: "OnHeartbeat",
1254 expected_type: GffType::ResRef,
1255 life: FieldLife::Live,
1256 required: false,
1257 absent: AbsentDefault::Unverified,
1258 children: None,
1259 constraint: None,
1260 },
1261 FieldSchema {
1262 label: "OnAnimEvent",
1263 expected_type: GffType::ResRef,
1264 life: FieldLife::Live,
1265 required: false,
1266 absent: AbsentDefault::Unverified,
1267 children: None,
1268 constraint: None,
1269 },
1270 FieldSchema {
1271 label: "OnHitBullet",
1272 expected_type: GffType::ResRef,
1273 life: FieldLife::Live,
1274 required: false,
1275 absent: AbsentDefault::Unverified,
1276 children: None,
1277 constraint: None,
1278 },
1279 FieldSchema {
1280 label: "OnHitFollower",
1281 expected_type: GffType::ResRef,
1282 life: FieldLife::Live,
1283 required: false,
1284 absent: AbsentDefault::Unverified,
1285 children: None,
1286 constraint: None,
1287 },
1288 FieldSchema {
1289 label: "OnDamage",
1290 expected_type: GffType::ResRef,
1291 life: FieldLife::Live,
1292 required: false,
1293 absent: AbsentDefault::Unverified,
1294 children: None,
1295 constraint: None,
1296 },
1297 FieldSchema {
1298 label: "OnDeath",
1299 expected_type: GffType::ResRef,
1300 life: FieldLife::Live,
1301 required: false,
1302 absent: AbsentDefault::Unverified,
1303 children: None,
1304 constraint: None,
1305 },
1306 FieldSchema {
1307 label: "OnFire",
1308 expected_type: GffType::ResRef,
1309 life: FieldLife::Live,
1310 required: false,
1311 absent: AbsentDefault::Unverified,
1312 children: None,
1313 constraint: None,
1314 },
1315 FieldSchema {
1316 label: "OnHitObstacle",
1317 expected_type: GffType::ResRef,
1318 life: FieldLife::Live,
1319 required: false,
1320 absent: AbsentDefault::Unverified,
1321 children: None,
1322 constraint: None,
1323 },
1324 FieldSchema {
1325 label: "OnTrackLoop",
1326 expected_type: GffType::ResRef,
1327 life: FieldLife::Live,
1328 required: false,
1329 absent: AbsentDefault::Unverified,
1330 children: None,
1331 constraint: None,
1332 },
1333];
1334
1335static SOUNDS_CHILDREN: &[FieldSchema] = &[
1337 FieldSchema {
1338 label: "Engine",
1339 expected_type: GffType::ResRef,
1340 life: FieldLife::Live,
1341 required: false,
1342 absent: AbsentDefault::Unverified,
1343 children: None,
1344 constraint: None,
1345 },
1346 FieldSchema {
1347 label: "Death",
1348 expected_type: GffType::ResRef,
1349 life: FieldLife::Live,
1350 required: false,
1351 absent: AbsentDefault::Unverified,
1352 children: None,
1353 constraint: None,
1354 },
1355];
1356
1357static BULLET_CHILDREN: &[FieldSchema] = &[
1364 FieldSchema {
1365 label: "Damage",
1366 expected_type: GffType::UInt32,
1367 life: FieldLife::Live,
1368 required: true,
1369 absent: AbsentDefault::Unverified,
1370 children: None,
1371 constraint: None,
1372 },
1373 FieldSchema {
1374 label: "Lifespan",
1375 expected_type: GffType::Single,
1376 life: FieldLife::Live,
1377 required: true,
1378 absent: AbsentDefault::Unverified,
1379 children: None,
1380 constraint: None,
1381 },
1382 FieldSchema {
1383 label: "Rate_Of_Fire",
1384 expected_type: GffType::Single,
1385 life: FieldLife::Live,
1386 required: true,
1387 absent: AbsentDefault::Unverified,
1388 children: None,
1389 constraint: None,
1390 },
1391 FieldSchema {
1392 label: "Speed",
1393 expected_type: GffType::Single,
1394 life: FieldLife::Live,
1395 required: true,
1396 absent: AbsentDefault::Unverified,
1397 children: None,
1398 constraint: None,
1399 },
1400 FieldSchema {
1401 label: "Target_Type",
1402 expected_type: GffType::UInt32,
1403 life: FieldLife::Live,
1404 required: true,
1405 absent: AbsentDefault::Unverified,
1406 children: None,
1407 constraint: None,
1408 },
1409 FieldSchema {
1410 label: "Bullet_Model",
1411 expected_type: GffType::ResRef,
1412 life: FieldLife::Live,
1413 required: false,
1414 absent: AbsentDefault::Unverified,
1415 children: None,
1416 constraint: None,
1417 },
1418 FieldSchema {
1419 label: "Collision_Sound",
1420 expected_type: GffType::ResRef,
1421 life: FieldLife::Live,
1422 required: false,
1423 absent: AbsentDefault::Unverified,
1424 children: None,
1425 constraint: None,
1426 },
1427];
1428
1429static GUN_BANK_CHILDREN: &[FieldSchema] = &[
1440 FieldSchema {
1441 label: "BankID",
1442 expected_type: GffType::UInt32,
1443 life: FieldLife::Live,
1444 required: true,
1445 absent: AbsentDefault::Unverified,
1446 children: None,
1447 constraint: None,
1448 },
1449 FieldSchema {
1450 label: "Gun_Model",
1451 expected_type: GffType::ResRef,
1452 life: FieldLife::Live,
1453 required: true,
1454 absent: AbsentDefault::Unverified,
1455 children: None,
1456 constraint: None,
1457 },
1458 FieldSchema {
1459 label: "Bullet",
1460 expected_type: GffType::Struct,
1461 life: FieldLife::Live,
1462 required: false,
1463 absent: AbsentDefault::Unverified,
1464 children: Some(BULLET_CHILDREN),
1465 constraint: None,
1466 },
1467 FieldSchema {
1468 label: "Fire_Sound",
1469 expected_type: GffType::ResRef,
1470 life: FieldLife::Live,
1471 required: false,
1472 absent: AbsentDefault::Unverified,
1473 children: None,
1474 constraint: None,
1475 },
1476 FieldSchema {
1477 label: "Sensing_Radius",
1478 expected_type: GffType::Single,
1479 life: FieldLife::Live,
1480 required: false,
1481 absent: AbsentDefault::Unverified,
1482 children: None,
1483 constraint: None,
1484 },
1485 FieldSchema {
1486 label: "Horiz_Spread",
1487 expected_type: GffType::Single,
1488 life: FieldLife::Live,
1489 required: false,
1490 absent: AbsentDefault::Unverified,
1491 children: None,
1492 constraint: None,
1493 },
1494 FieldSchema {
1495 label: "Vert_Spread",
1496 expected_type: GffType::Single,
1497 life: FieldLife::Live,
1498 required: false,
1499 absent: AbsentDefault::Unverified,
1500 children: None,
1501 constraint: None,
1502 },
1503 FieldSchema {
1504 label: "Inaccuracy",
1505 expected_type: GffType::Single,
1506 life: FieldLife::Live,
1507 required: false,
1508 absent: AbsentDefault::Unverified,
1509 children: None,
1510 constraint: None,
1511 },
1512];
1513
1514static PLAYER_CHILDREN: &[FieldSchema] = &[
1517 FieldSchema {
1518 label: "Models",
1519 expected_type: GffType::List,
1520 life: FieldLife::Live,
1521 required: false,
1522 absent: AbsentDefault::Unverified,
1523 children: Some(MODEL_CHILDREN),
1524 constraint: None,
1525 },
1526 FieldSchema {
1527 label: "Track",
1528 expected_type: GffType::ResRef,
1529 life: FieldLife::Live,
1530 required: false,
1531 absent: AbsentDefault::Unverified,
1532 children: None,
1533 constraint: None,
1534 },
1535 FieldSchema {
1536 label: "Hit_Points",
1537 expected_type: GffType::UInt32,
1538 life: FieldLife::Live,
1539 required: false,
1540 absent: AbsentDefault::Unverified,
1541 children: None,
1542 constraint: None,
1543 },
1544 FieldSchema {
1545 label: "Max_HPs",
1546 expected_type: GffType::UInt32,
1547 life: FieldLife::Live,
1548 required: false,
1549 absent: AbsentDefault::Unverified,
1550 children: None,
1551 constraint: None,
1552 },
1553 FieldSchema {
1554 label: "Sphere_Radius",
1555 expected_type: GffType::Single,
1556 life: FieldLife::Live,
1557 required: false,
1558 absent: AbsentDefault::Unverified,
1559 children: None,
1560 constraint: None,
1561 },
1562 FieldSchema {
1563 label: "Invince_Period",
1564 expected_type: GffType::Single,
1565 life: FieldLife::Live,
1566 required: false,
1567 absent: AbsentDefault::Unverified,
1568 children: None,
1569 constraint: None,
1570 },
1571 FieldSchema {
1572 label: "Bump_Damage",
1573 expected_type: GffType::Int32,
1574 life: FieldLife::Live,
1575 required: false,
1576 absent: AbsentDefault::Unverified,
1577 children: None,
1578 constraint: None,
1579 },
1580 FieldSchema {
1581 label: "Num_Loops",
1582 expected_type: GffType::Int32,
1583 life: FieldLife::Live,
1584 required: false,
1585 absent: AbsentDefault::Unverified,
1586 children: None,
1587 constraint: None,
1588 },
1589 FieldSchema {
1590 label: "Scripts",
1591 expected_type: GffType::Struct,
1592 life: FieldLife::Live,
1593 required: false,
1594 absent: AbsentDefault::Unverified,
1595 children: Some(VEHICLE_SCRIPTS_CHILDREN),
1596 constraint: None,
1597 },
1598 FieldSchema {
1599 label: "Sounds",
1600 expected_type: GffType::Struct,
1601 life: FieldLife::Live,
1602 required: false,
1603 absent: AbsentDefault::Unverified,
1604 children: Some(SOUNDS_CHILDREN),
1605 constraint: None,
1606 },
1607 FieldSchema {
1608 label: "Gun_Banks",
1609 expected_type: GffType::List,
1610 life: FieldLife::Live,
1611 required: false,
1612 absent: AbsentDefault::Unverified,
1613 children: Some(GUN_BANK_CHILDREN),
1614 constraint: None,
1615 },
1616 FieldSchema {
1617 label: "Camera",
1618 expected_type: GffType::ResRef,
1619 life: FieldLife::Live,
1620 required: false,
1621 absent: AbsentDefault::Unverified,
1622 children: None,
1623 constraint: None,
1624 },
1625 FieldSchema {
1626 label: "CameraRotate",
1627 expected_type: GffType::UInt8,
1628 life: FieldLife::Live,
1629 required: false,
1630 absent: AbsentDefault::Unverified,
1631 children: None,
1632 constraint: None,
1633 },
1634 FieldSchema {
1635 label: "Minimum_Speed",
1636 expected_type: GffType::Single,
1637 life: FieldLife::Live,
1638 required: false,
1639 absent: AbsentDefault::Unverified,
1640 children: None,
1641 constraint: None,
1642 },
1643 FieldSchema {
1644 label: "Maximum_Speed",
1645 expected_type: GffType::Single,
1646 life: FieldLife::Live,
1647 required: false,
1648 absent: AbsentDefault::Unverified,
1649 children: None,
1650 constraint: None,
1651 },
1652 FieldSchema {
1653 label: "Accel_Secs",
1654 expected_type: GffType::Single,
1655 life: FieldLife::Live,
1656 required: false,
1657 absent: AbsentDefault::Unverified,
1658 children: None,
1659 constraint: None,
1660 },
1661 FieldSchema {
1662 label: "TunnelXPos",
1663 expected_type: GffType::Single,
1664 life: FieldLife::Live,
1665 required: false,
1666 absent: AbsentDefault::Unverified,
1667 children: None,
1668 constraint: None,
1669 },
1670 FieldSchema {
1671 label: "TunnelXNeg",
1672 expected_type: GffType::Single,
1673 life: FieldLife::Live,
1674 required: false,
1675 absent: AbsentDefault::Unverified,
1676 children: None,
1677 constraint: None,
1678 },
1679 FieldSchema {
1680 label: "TunnelYPos",
1681 expected_type: GffType::Single,
1682 life: FieldLife::Live,
1683 required: false,
1684 absent: AbsentDefault::Unverified,
1685 children: None,
1686 constraint: None,
1687 },
1688 FieldSchema {
1689 label: "TunnelYNeg",
1690 expected_type: GffType::Single,
1691 life: FieldLife::Live,
1692 required: false,
1693 absent: AbsentDefault::Unverified,
1694 children: None,
1695 constraint: None,
1696 },
1697 FieldSchema {
1698 label: "TunnelZPos",
1699 expected_type: GffType::Single,
1700 life: FieldLife::Live,
1701 required: false,
1702 absent: AbsentDefault::Unverified,
1703 children: None,
1704 constraint: None,
1705 },
1706 FieldSchema {
1707 label: "TunnelZNeg",
1708 expected_type: GffType::Single,
1709 life: FieldLife::Live,
1710 required: false,
1711 absent: AbsentDefault::Unverified,
1712 children: None,
1713 constraint: None,
1714 },
1715 FieldSchema {
1716 label: "TunnelInfinite",
1717 expected_type: GffType::Vector3,
1718 life: FieldLife::Live,
1719 required: false,
1720 absent: AbsentDefault::Unverified,
1721 children: None,
1722 constraint: None,
1723 },
1724 FieldSchema {
1725 label: "Start_Offset_X",
1726 expected_type: GffType::Single,
1727 life: FieldLife::Live,
1728 required: false,
1729 absent: AbsentDefault::Unverified,
1730 children: None,
1731 constraint: None,
1732 },
1733 FieldSchema {
1734 label: "Start_Offset_Y",
1735 expected_type: GffType::Single,
1736 life: FieldLife::Live,
1737 required: false,
1738 absent: AbsentDefault::Unverified,
1739 children: None,
1740 constraint: None,
1741 },
1742 FieldSchema {
1743 label: "Start_Offset_Z",
1744 expected_type: GffType::Single,
1745 life: FieldLife::Live,
1746 required: false,
1747 absent: AbsentDefault::Unverified,
1748 children: None,
1749 constraint: None,
1750 },
1751 FieldSchema {
1752 label: "Target_Offset_X",
1753 expected_type: GffType::Single,
1754 life: FieldLife::Live,
1755 required: false,
1756 absent: AbsentDefault::Unverified,
1757 children: None,
1758 constraint: None,
1759 },
1760 FieldSchema {
1761 label: "Target_Offset_Y",
1762 expected_type: GffType::Single,
1763 life: FieldLife::Live,
1764 required: false,
1765 absent: AbsentDefault::Unverified,
1766 children: None,
1767 constraint: None,
1768 },
1769 FieldSchema {
1770 label: "Target_Offset_Z",
1771 expected_type: GffType::Single,
1772 life: FieldLife::Live,
1773 required: false,
1774 absent: AbsentDefault::Unverified,
1775 children: None,
1776 constraint: None,
1777 },
1778];
1779
1780static ENEMY_CHILDREN: &[FieldSchema] = &[
1782 FieldSchema {
1783 label: "Models",
1784 expected_type: GffType::List,
1785 life: FieldLife::Live,
1786 required: false,
1787 absent: AbsentDefault::Unverified,
1788 children: Some(MODEL_CHILDREN),
1789 constraint: None,
1790 },
1791 FieldSchema {
1792 label: "Track",
1793 expected_type: GffType::ResRef,
1794 life: FieldLife::Live,
1795 required: false,
1796 absent: AbsentDefault::Unverified,
1797 children: None,
1798 constraint: None,
1799 },
1800 FieldSchema {
1801 label: "Hit_Points",
1802 expected_type: GffType::UInt32,
1803 life: FieldLife::Live,
1804 required: false,
1805 absent: AbsentDefault::Unverified,
1806 children: None,
1807 constraint: None,
1808 },
1809 FieldSchema {
1810 label: "Max_HPs",
1811 expected_type: GffType::UInt32,
1812 life: FieldLife::Live,
1813 required: false,
1814 absent: AbsentDefault::Unverified,
1815 children: None,
1816 constraint: None,
1817 },
1818 FieldSchema {
1819 label: "Sphere_Radius",
1820 expected_type: GffType::Single,
1821 life: FieldLife::Live,
1822 required: false,
1823 absent: AbsentDefault::Unverified,
1824 children: None,
1825 constraint: None,
1826 },
1827 FieldSchema {
1828 label: "Invince_Period",
1829 expected_type: GffType::Single,
1830 life: FieldLife::Live,
1831 required: false,
1832 absent: AbsentDefault::Unverified,
1833 children: None,
1834 constraint: None,
1835 },
1836 FieldSchema {
1837 label: "Bump_Damage",
1838 expected_type: GffType::Int32,
1839 life: FieldLife::Live,
1840 required: false,
1841 absent: AbsentDefault::Unverified,
1842 children: None,
1843 constraint: None,
1844 },
1845 FieldSchema {
1846 label: "Num_Loops",
1847 expected_type: GffType::Int32,
1848 life: FieldLife::Live,
1849 required: false,
1850 absent: AbsentDefault::Unverified,
1851 children: None,
1852 constraint: None,
1853 },
1854 FieldSchema {
1855 label: "Scripts",
1856 expected_type: GffType::Struct,
1857 life: FieldLife::Live,
1858 required: false,
1859 absent: AbsentDefault::Unverified,
1860 children: Some(VEHICLE_SCRIPTS_CHILDREN),
1861 constraint: None,
1862 },
1863 FieldSchema {
1864 label: "Sounds",
1865 expected_type: GffType::Struct,
1866 life: FieldLife::Live,
1867 required: false,
1868 absent: AbsentDefault::Unverified,
1869 children: Some(SOUNDS_CHILDREN),
1870 constraint: None,
1871 },
1872 FieldSchema {
1873 label: "Gun_Banks",
1874 expected_type: GffType::List,
1875 life: FieldLife::Live,
1876 required: false,
1877 absent: AbsentDefault::Unverified,
1878 children: Some(GUN_BANK_CHILDREN),
1879 constraint: None,
1880 },
1881 FieldSchema {
1882 label: "Trigger",
1883 expected_type: GffType::UInt8,
1884 life: FieldLife::Live,
1885 required: false,
1886 absent: AbsentDefault::Unverified,
1887 children: None,
1888 constraint: None,
1889 },
1890];
1891
1892static OBSTACLE_CHILDREN: &[FieldSchema] = &[
1894 FieldSchema {
1895 label: "Name",
1896 expected_type: GffType::ResRef,
1897 life: FieldLife::Live,
1898 required: false,
1899 absent: AbsentDefault::Unverified,
1900 children: None,
1901 constraint: None,
1902 },
1903 FieldSchema {
1904 label: "Scripts",
1905 expected_type: GffType::Struct,
1906 life: FieldLife::Live,
1907 required: false,
1908 absent: AbsentDefault::Unverified,
1909 children: Some(OBJECT_SCRIPTS_CHILDREN),
1910 constraint: None,
1911 },
1912];
1913
1914static MOUSE_CHILDREN: &[FieldSchema] = &[
1916 FieldSchema {
1917 label: "AxisX",
1918 expected_type: GffType::UInt32,
1919 life: FieldLife::Live,
1920 required: false,
1921 absent: AbsentDefault::Unverified,
1922 children: None,
1923 constraint: None,
1924 },
1925 FieldSchema {
1926 label: "AxisY",
1927 expected_type: GffType::UInt32,
1928 life: FieldLife::Live,
1929 required: false,
1930 absent: AbsentDefault::Unverified,
1931 children: None,
1932 constraint: None,
1933 },
1934 FieldSchema {
1935 label: "FlipAxisX",
1936 expected_type: GffType::UInt8,
1937 life: FieldLife::Live,
1938 required: false,
1939 absent: AbsentDefault::Unverified,
1940 children: None,
1941 constraint: None,
1942 },
1943 FieldSchema {
1944 label: "FlipAxisY",
1945 expected_type: GffType::UInt8,
1946 life: FieldLife::Live,
1947 required: false,
1948 absent: AbsentDefault::Unverified,
1949 children: None,
1950 constraint: None,
1951 },
1952];
1953
1954pub(crate) static MINI_GAME_CHILDREN: &[FieldSchema] = &[
1956 FieldSchema {
1957 label: "Type",
1958 expected_type: GffType::UInt32,
1959 life: FieldLife::Live,
1960 required: false,
1961 absent: AbsentDefault::Unverified,
1962 children: None,
1963 constraint: None,
1964 },
1965 FieldSchema {
1966 label: "MovementPerSec",
1967 expected_type: GffType::Single,
1968 life: FieldLife::Live,
1969 required: false,
1970 absent: AbsentDefault::Unverified,
1971 children: None,
1972 constraint: None,
1973 },
1974 FieldSchema {
1975 label: "LateralAccel",
1976 expected_type: GffType::Single,
1977 life: FieldLife::Live,
1978 required: false,
1979 absent: AbsentDefault::Unverified,
1980 children: None,
1981 constraint: None,
1982 },
1983 FieldSchema {
1984 label: "Bump_Plane",
1985 expected_type: GffType::UInt32,
1986 life: FieldLife::Live,
1987 required: false,
1988 absent: AbsentDefault::Unverified,
1989 children: None,
1990 constraint: None,
1991 },
1992 FieldSchema {
1993 label: "DoBumping",
1994 expected_type: GffType::UInt8,
1995 life: FieldLife::Live,
1996 required: false,
1997 absent: AbsentDefault::Unverified,
1998 children: None,
1999 constraint: None,
2000 },
2001 FieldSchema {
2002 label: "UseInertia",
2003 expected_type: GffType::UInt8,
2004 life: FieldLife::Live,
2005 required: false,
2006 absent: AbsentDefault::Unverified,
2007 children: None,
2008 constraint: None,
2009 },
2010 FieldSchema {
2011 label: "DOF",
2012 expected_type: GffType::UInt32,
2013 life: FieldLife::Live,
2014 required: false,
2015 absent: AbsentDefault::Unverified,
2016 children: None,
2017 constraint: None,
2018 },
2019 FieldSchema {
2020 label: "Music",
2021 expected_type: GffType::ResRef,
2022 life: FieldLife::Live,
2023 required: false,
2024 absent: AbsentDefault::Unverified,
2025 children: None,
2026 constraint: None,
2027 },
2028 FieldSchema {
2029 label: "Far_Clip",
2030 expected_type: GffType::Single,
2031 life: FieldLife::Live,
2032 required: false,
2033 absent: AbsentDefault::Unverified,
2034 children: None,
2035 constraint: None,
2036 },
2037 FieldSchema {
2038 label: "Near_Clip",
2039 expected_type: GffType::Single,
2040 life: FieldLife::Live,
2041 required: false,
2042 absent: AbsentDefault::Unverified,
2043 children: None,
2044 constraint: None,
2045 },
2046 FieldSchema {
2047 label: "CameraViewAngle",
2048 expected_type: GffType::Single,
2049 life: FieldLife::Live,
2050 required: false,
2051 absent: AbsentDefault::Unverified,
2052 children: None,
2053 constraint: None,
2054 },
2055 FieldSchema {
2056 label: "Player",
2057 expected_type: GffType::Struct,
2058 life: FieldLife::Live,
2059 required: false,
2060 absent: AbsentDefault::Unverified,
2061 children: Some(PLAYER_CHILDREN),
2062 constraint: None,
2063 },
2064 FieldSchema {
2065 label: "Mouse",
2066 expected_type: GffType::Struct,
2067 life: FieldLife::Live,
2068 required: false,
2069 absent: AbsentDefault::Unverified,
2070 children: Some(MOUSE_CHILDREN),
2071 constraint: None,
2072 },
2073 FieldSchema {
2074 label: "Enemies",
2075 expected_type: GffType::List,
2076 life: FieldLife::Live,
2077 required: false,
2078 absent: AbsentDefault::Unverified,
2079 children: Some(ENEMY_CHILDREN),
2080 constraint: None,
2081 },
2082 FieldSchema {
2083 label: "Obstacles",
2084 expected_type: GffType::List,
2085 life: FieldLife::Live,
2086 required: false,
2087 absent: AbsentDefault::Unverified,
2088 children: Some(OBSTACLE_CHILDREN),
2089 constraint: None,
2090 },
2091];
2092
2093#[cfg(test)]
2094mod tests {
2095 use super::*;
2096
2097 use crate::are::{read_are_from_bytes, write_are_to_vec, Are};
2098 use rakata_formats::Gff;
2099
2100 fn assert_declared(structure: &GffStruct, schema: &[FieldSchema], path: &str) {
2108 for field in &structure.fields {
2109 let entry = schema
2110 .iter()
2111 .find(|f| f.label == field.label.as_str())
2112 .unwrap_or_else(|| {
2113 panic!("{path}.{} is written but not in the schema", field.label)
2114 });
2115 let child_path = format!("{path}.{}", field.label);
2116 match (&field.value, entry.children) {
2117 (GffValue::Struct(inner), Some(children)) => {
2118 assert_declared(inner, children, &child_path);
2119 }
2120 (GffValue::List(items), Some(children)) => {
2121 for item in items {
2122 assert_declared(item, children, &child_path);
2123 }
2124 }
2125 _ => {}
2126 }
2127 }
2128 }
2129
2130 #[test]
2131 fn every_written_minigame_label_is_in_the_schema() {
2132 let vehicle = || AreMiniGameVehicle {
2138 models: vec![AreMiniGameModel::default()],
2139 gun_banks: vec![AreMiniGameGunBank {
2140 bullet: Some(AreMiniGameBullet::default()),
2141 targeting: Some(AreMiniGameTargeting::default()),
2142 ..AreMiniGameGunBank::default()
2143 }],
2144 ..AreMiniGameVehicle::default()
2145 };
2146 let mini_game = AreMiniGame {
2147 player: Some(AreMiniGamePlayer {
2148 vehicle: vehicle(),
2149 ..AreMiniGamePlayer::default()
2150 }),
2151 mouse: Some(AreMiniGameMouse::default()),
2152 enemies: vec![AreMiniGameEnemy {
2153 vehicle: vehicle(),
2154 trigger: 1,
2155 }],
2156 obstacles: vec![AreMiniGameObstacle::default()],
2157 ..AreMiniGame::default()
2158 };
2159
2160 let written = mini_game.to_struct();
2161 let deepest = written
2165 .field("Player")
2166 .and_then(|v| match v {
2167 GffValue::Struct(p) => p.field("Gun_Banks"),
2168 _ => None,
2169 })
2170 .and_then(|v| match v {
2171 GffValue::List(banks) => banks.first().and_then(|b| b.field("Bullet")),
2172 _ => None,
2173 });
2174 assert!(
2175 matches!(deepest, Some(GffValue::Struct(_))),
2176 "the fixture must reach MiniGame.Player.Gun_Banks[].Bullet"
2177 );
2178
2179 assert_declared(&written, MINI_GAME_CHILDREN, "MiniGame");
2180 }
2181
2182 #[test]
2183 fn type_mismatch_on_mini_game_field_is_error() {
2184 let mut root = GffStruct::new(-1);
2185 root.push_field("MiniGame", GffValue::UInt32(7));
2186 let gff = Gff::new(*b"ARE ", root);
2187 let err = Are::from_gff(&gff).expect_err("must fail");
2188 assert!(matches!(
2189 err,
2190 AreError::TypeMismatch {
2191 field: "MiniGame",
2192 expected: "Struct"
2193 }
2194 ));
2195 }
2196
2197 #[test]
2198 fn mini_game_struct_roundtrips() {
2199 let mg = AreMiniGame {
2200 mini_game_type: 1,
2201 movement_per_sec: 25.0,
2202 lateral_accel: 45.0,
2203 bump_plane: 2,
2204 do_bumping: true,
2205 use_inertia: true,
2206 dof: 3,
2207 music: ResRef::new("mus_swoop").expect("valid test resref"),
2208 far_clip: 200.0,
2209 near_clip: 0.5,
2210 camera_view_angle: 70.0,
2211 player: Some(AreMiniGamePlayer {
2212 vehicle: AreMiniGameVehicle {
2213 models: vec![AreMiniGameModel {
2214 model: ResRef::new("swoopbike").expect("valid test resref"),
2215 rotating_model: false,
2216 }],
2217 track: ResRef::new("trk_race01").expect("valid test resref"),
2218 hit_points: 120,
2219 max_hps: 150,
2220 sphere_radius: 4.5,
2221 invince_period: 0.75,
2222 bump_damage: 9,
2223 num_loops: 3,
2224 scripts: AreMiniGameVehicleScripts {
2225 object: AreMiniGameObjectScripts {
2226 on_heartbeat: ResRef::new("mg_beat").expect("valid test resref"),
2227 ..AreMiniGameObjectScripts::default()
2228 },
2229 on_fire: ResRef::new("mg_fire").expect("valid test resref"),
2230 ..AreMiniGameVehicleScripts::default()
2231 },
2232 sounds: AreMiniGameSounds {
2233 engine: ResRef::new("snd_engine").expect("valid test resref"),
2234 death: ResRef::new("snd_boom").expect("valid test resref"),
2235 },
2236 gun_banks: vec![AreMiniGameGunBank {
2237 bank_id: 0,
2238 gun_model: ResRef::new("mgg_turret").expect("valid test resref"),
2239 bullet: Some(AreMiniGameBullet {
2240 damage: 30,
2241 speed: 300.0,
2242 ..AreMiniGameBullet::default()
2243 }),
2244 ..AreMiniGameGunBank::default()
2245 }],
2246 },
2247 camera: ResRef::new("cam_swoop").expect("valid test resref"),
2248 camera_rotate: true,
2249 ..AreMiniGamePlayer::default()
2250 }),
2251 mouse: Some(AreMiniGameMouse {
2253 axis_x: 1,
2254 axis_y: 2,
2255 flip_axis_x: true,
2256 flip_axis_y: false,
2257 }),
2258 enemies: vec![AreMiniGameEnemy {
2259 vehicle: AreMiniGameVehicle {
2260 track: ResRef::new("trk_enemy01").expect("valid test resref"),
2261 hit_points: 40,
2262 max_hps: 40,
2263 sphere_radius: 2.0,
2264 num_loops: -1,
2265 ..AreMiniGameVehicle::default()
2266 },
2267 trigger: 1,
2268 }],
2269 obstacles: vec![AreMiniGameObstacle {
2270 name: ResRef::new("obs_rock").expect("valid test resref"),
2271 scripts: AreMiniGameObjectScripts {
2272 on_hit_follower: ResRef::new("mg_bump").expect("valid test resref"),
2273 ..AreMiniGameObjectScripts::default()
2274 },
2275 }],
2276 };
2277
2278 let mut are = Are::new();
2279 are.mini_game = Some(mg);
2280
2281 let encoded = write_are_to_vec(&are).expect("encode");
2282 let reparsed = read_are_from_bytes(&encoded).expect("decode");
2283
2284 let mg_out = reparsed.mini_game.expect("MiniGame must survive roundtrip");
2285 assert_eq!(mg_out.mini_game_type, 1);
2286 assert!((mg_out.movement_per_sec - 25.0).abs() < f32::EPSILON);
2287 assert!((mg_out.lateral_accel - 45.0).abs() < f32::EPSILON);
2288 assert_eq!(mg_out.bump_plane, 2);
2289 assert!(mg_out.do_bumping);
2290 assert!(mg_out.use_inertia);
2291 assert_eq!(mg_out.dof, 3);
2292 assert_eq!(mg_out.music, "mus_swoop");
2293 assert!((mg_out.far_clip - 200.0).abs() < f32::EPSILON);
2294 assert!((mg_out.near_clip - 0.5).abs() < f32::EPSILON);
2295 assert!((mg_out.camera_view_angle - 70.0).abs() < f32::EPSILON);
2296
2297 let player = mg_out.player.expect("Player must survive roundtrip");
2298 assert_eq!(player.vehicle.models.len(), 1);
2299 assert_eq!(player.vehicle.models[0].model, "swoopbike");
2300 assert!(!player.vehicle.models[0].rotating_model);
2301 assert_eq!(player.vehicle.track, "trk_race01");
2302 assert_eq!(player.vehicle.hit_points, 120);
2303 assert_eq!(player.vehicle.max_hps, 150);
2304 assert!((player.vehicle.sphere_radius - 4.5).abs() < f32::EPSILON);
2305 assert!((player.vehicle.invince_period - 0.75).abs() < f32::EPSILON);
2306 assert_eq!(player.vehicle.bump_damage, 9);
2307 assert_eq!(player.vehicle.num_loops, 3);
2308 assert_eq!(player.vehicle.scripts.object.on_heartbeat, "mg_beat");
2309 assert_eq!(player.vehicle.scripts.on_fire, "mg_fire");
2310 assert_eq!(player.vehicle.sounds.engine, "snd_engine");
2311 assert_eq!(player.vehicle.sounds.death, "snd_boom");
2312 assert_eq!(player.camera, "cam_swoop");
2313 assert!(player.camera_rotate);
2314 let mouse = mg_out.mouse.as_ref().expect("Mouse must survive roundtrip");
2315 assert_eq!(mouse.axis_x, 1);
2316 assert_eq!(mouse.axis_y, 2);
2317 assert!(mouse.flip_axis_x);
2318 assert!(!mouse.flip_axis_y);
2319 assert_eq!(mg_out.enemies.len(), 1);
2320 assert!(mg_out.enemies[0].vehicle.models.is_empty());
2321 assert_eq!(mg_out.enemies[0].vehicle.track, "trk_enemy01");
2322 assert_eq!(mg_out.enemies[0].vehicle.hit_points, 40);
2323 assert_eq!(mg_out.enemies[0].vehicle.num_loops, -1);
2324 assert_eq!(mg_out.obstacles.len(), 1);
2325 assert_eq!(mg_out.obstacles[0].name, "obs_rock");
2326 assert_eq!(mg_out.obstacles[0].scripts.on_hit_follower, "mg_bump");
2327
2328 let encoded_gff = are.to_gff();
2334 let Some(GffValue::Struct(mini_game)) = encoded_gff.root.field("MiniGame") else {
2335 panic!("MiniGame must be a struct");
2336 };
2337 for label in ["Mouse", "Enemies", "Obstacles"] {
2338 assert!(
2339 mini_game.field(label).is_some(),
2340 "{label} belongs on MiniGame"
2341 );
2342 }
2343 let Some(GffValue::Struct(player_struct)) = mini_game.field("Player") else {
2344 panic!("Player must be a struct");
2345 };
2346 for label in ["Mouse", "Enemies", "Obstacles"] {
2347 assert!(
2348 player_struct.field(label).is_none(),
2349 "{label} must not be written under Player"
2350 );
2351 }
2352 }
2353
2354 #[test]
2355 fn a_gun_bank_without_a_bullet_stays_without_one() {
2356 let bank = AreMiniGameGunBank::from_struct(&GffStruct::new(0), "test.Bullet")
2361 .expect("a bank with no fields still parses");
2362 assert!(bank.bullet.is_none());
2363 assert_eq!(bank.bank_id, BANK_ID_NONE);
2364
2365 let written = bank.to_struct();
2366 assert!(
2367 written.field("Bullet").is_none(),
2368 "an absent Bullet must not be invented on write"
2369 );
2370 }
2371
2372 #[test]
2373 fn targeting_needs_all_four_labels_to_count_as_present() {
2374 let mut partial = GffStruct::new(0);
2379 partial.push_field("Sensing_Radius", GffValue::Single(200.0));
2380 partial.push_field("Horiz_Spread", GffValue::Single(70.0));
2381 partial.push_field("Vert_Spread", GffValue::Single(70.0));
2382 let bank = AreMiniGameGunBank::from_struct(&partial, "test.Bullet").expect("parses");
2383 assert!(bank.targeting.is_none(), "three of four is not a set");
2384
2385 let mut full = partial.clone();
2386 full.push_field("Inaccuracy", GffValue::Single(0.01));
2387 let bank = AreMiniGameGunBank::from_struct(&full, "test.Bullet").expect("parses");
2388 let targeting = bank.targeting.expect("all four present");
2389 assert!((targeting.sensing_radius - 200.0).abs() < f32::EPSILON);
2390 assert!((targeting.inaccuracy - 0.01).abs() < f32::EPSILON);
2391
2392 let player_bank =
2394 AreMiniGameGunBank::from_struct(&GffStruct::new(0), "test.Bullet").expect("parses");
2395 let written = player_bank.to_struct();
2396 for label in AreMiniGameTargeting::LABELS {
2397 assert!(
2398 written.field(label).is_none(),
2399 "{label} belongs to enemy banks only"
2400 );
2401 }
2402 }
2403
2404 #[test]
2405 fn the_player_movement_block_round_trips_and_defaults_to_its_sentinels() {
2406 let bare = AreMiniGamePlayer::from_struct(&GffStruct::new(0)).expect("parses");
2411 assert!((bare.minimum_speed - -1.0).abs() < f32::EPSILON);
2412 assert!((bare.maximum_speed - 100.0).abs() < f32::EPSILON);
2413 assert!((bare.accel_secs - -1.0).abs() < f32::EPSILON);
2414 assert_eq!(bare.tunnel_infinite, [0.0; 3]);
2415
2416 let player = AreMiniGamePlayer {
2417 minimum_speed: 5.0,
2418 maximum_speed: 42.5,
2419 accel_secs: 2.5,
2420 tunnel_x_pos: 45.0,
2421 tunnel_x_neg: 2.0,
2422 tunnel_y_pos: 3.0,
2423 tunnel_y_neg: 4.0,
2424 tunnel_z_pos: 9999.0,
2425 tunnel_z_neg: -9999.0,
2426 tunnel_infinite: [0.0, 0.0, 1.0],
2427 start_offset_x: 7.0,
2428 start_offset_y: 8.0,
2429 start_offset_z: 9.0,
2430 target_offset_x: 10.0,
2431 target_offset_y: 11.0,
2432 target_offset_z: -5.0,
2433 ..AreMiniGamePlayer::default()
2434 };
2435
2436 let back = AreMiniGamePlayer::from_struct(&player.to_struct()).expect("parses");
2437 assert_eq!(back, player);
2438
2439 let written = player.to_struct();
2442 assert!(matches!(
2443 written.field("TunnelInfinite"),
2444 Some(GffValue::Vector3([0.0, 0.0, 1.0]))
2445 ));
2446 assert!(matches!(
2447 written.field("TunnelZPos"),
2448 Some(GffValue::Single(_))
2449 ));
2450 }
2451
2452 #[test]
2453 fn an_obstacle_writes_the_base_script_set_and_no_vehicle_slots() {
2454 let mut are = Are::new();
2461 are.mini_game = Some(AreMiniGame {
2462 obstacles: vec![AreMiniGameObstacle::default()],
2463 ..AreMiniGame::default()
2464 });
2465
2466 let encoded = are.to_gff();
2467 let Some(GffValue::Struct(mini_game)) = encoded.root.field("MiniGame") else {
2468 panic!("MiniGame must be a struct");
2469 };
2470 let Some(GffValue::List(obstacles)) = mini_game.field("Obstacles") else {
2471 panic!("Obstacles must be a list");
2472 };
2473 let Some(GffValue::Struct(scripts)) = obstacles[0].field("Scripts") else {
2474 panic!("an obstacle must carry a Scripts struct");
2475 };
2476
2477 for label in [
2478 "OnCreate",
2479 "OnHeartbeat",
2480 "OnAnimEvent",
2481 "OnHitBullet",
2482 "OnHitFollower",
2483 ] {
2484 assert!(
2485 scripts.field(label).is_some(),
2486 "{label} is in the base script set"
2487 );
2488 }
2489 for label in [
2490 "OnDamage",
2491 "OnDeath",
2492 "OnFire",
2493 "OnHitObstacle",
2494 "OnTrackLoop",
2495 ] {
2496 assert!(
2497 scripts.field(label).is_none(),
2498 "{label} belongs to the vehicle override, not an obstacle"
2499 );
2500 }
2501 }
2502
2503 #[test]
2504 fn an_area_without_a_mouse_struct_does_not_gain_one() {
2505 let mut are = Are::new();
2509 are.mini_game = Some(AreMiniGame {
2510 mouse: None,
2511 ..AreMiniGame::default()
2512 });
2513
2514 let encoded = are.to_gff();
2515 let Some(GffValue::Struct(mini_game)) = encoded.root.field("MiniGame") else {
2516 panic!("MiniGame must be a struct");
2517 };
2518 assert!(mini_game.field("Mouse").is_none());
2519
2520 let reparsed = Are::from_gff(&encoded).expect("parses");
2521 assert!(reparsed.mini_game.expect("minigame").mouse.is_none());
2522 }
2523}